diff --git a/app/Livewire/Pages/Discussions/Index.php b/app/Livewire/Pages/Discussions/Index.php index 9e845e02..977384b5 100644 --- a/app/Livewire/Pages/Discussions/Index.php +++ b/app/Livewire/Pages/Discussions/Index.php @@ -57,7 +57,7 @@ public function tagExists(string $tag): bool public function render(): View { /** @var DiscussionQueryBuilder $query */ - $query = Discussion::with('tags') // @phpstan-ignore-line + $query = Discussion::with(['tags', 'replies', 'user']) // @phpstan-ignore-line ->withCount('replies') ->forLocale($this->locale) ->notPinned(); diff --git a/app/Livewire/Pages/Forum/Index.php b/app/Livewire/Pages/Forum/Index.php index c8a519d4..14ec2378 100644 --- a/app/Livewire/Pages/Forum/Index.php +++ b/app/Livewire/Pages/Forum/Index.php @@ -139,7 +139,7 @@ protected function applyUnAnswer(Builder $query): Builder public function render(): View { - $query = Thread::with('channels') + $query = Thread::with(['channels', 'user']) ->orderByDesc('created_at'); $query = $this->applyChannel($query); diff --git a/resources/js/app.js b/resources/js/app.js index c5495184..449d001d 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -5,14 +5,10 @@ import intersect from '@alpinejs/intersect' import Tooltip from '@ryangjchandler/alpine-tooltip' import collapse from '@alpinejs/collapse' -import './elements' -import { registerHeader } from './utils/header' import './utils/helpers' import './utils/scrollspy' import './utils/clipboard' -registerHeader() - Alpine.plugin(intersect) Alpine.plugin(Tooltip) Alpine.plugin(collapse) diff --git a/resources/js/components/datepicker.js b/resources/js/components/datepicker.js deleted file mode 100644 index 79a8af2f..00000000 --- a/resources/js/components/datepicker.js +++ /dev/null @@ -1,130 +0,0 @@ -export default () => ({ - datePickerOpen: false, - datePickerValue: '', - datePickerRealValue: '', - datePickerFormat: 'd M, Y', - datePickerMonth: '', - datePickerYear: '', - datePickerDay: '', - datePickerDaysInMonth: [], - datePickerBlankDaysInMonth: [], - datePickerMonthNames: [ - 'Janvier', - 'Février', - 'Mars', - 'Avril', - 'Mai', - 'Juin', - 'Juillet', - 'Aout', - 'Septembre', - 'Octobre', - 'Novembre', - 'Décembre', - ], - datePickerDays: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'], - - datePickerDayClicked(day) { - let selectedDate = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth, day)) - this.datePickerDay = day - this.datePickerValue = this.datePickerFormatDate(selectedDate, this.datePickerFormat) - this.datePickerRealValue = selectedDate - this.datePickerIsSelectedDate(day) - this.datePickerOpen = false - }, - - datePickerPreviousMonth() { - if (this.datePickerMonth === 0) { - this.datePickerYear-- - this.datePickerMonth = 12 - } - this.datePickerMonth-- - this.datePickerCalculateDays() - }, - - datePickerNextMonth() { - if (this.datePickerMonth === 11) { - this.datePickerMonth = 0 - this.datePickerYear++ - } else { - this.datePickerMonth++ - } - this.datePickerCalculateDays() - }, - - datePickerIsSelectedDate(day) { - const d = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth, day)) - return this.datePickerValue === this.datePickerFormatDate(d, this.datePickerFormat) - }, - - datePickerIsToday(day) { - const today = new Date() - const d = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth, day)) - return today.toDateString() === d.toDateString() - }, - - datePickerCalculateDays() { - let daysInMonth = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth + 1, 0)).getUTCDate() - // find where to start calendar day of week - let dayOfWeek = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth)).getUTCDay() - let blankDaysArray = [] - for (let i = 1; i <= dayOfWeek; i++) { - blankDaysArray.push(i) - } - let daysArray = [] - for (let i = 1; i <= daysInMonth; i++) { - daysArray.push(i) - } - this.datePickerBlankDaysInMonth = blankDaysArray - this.datePickerDaysInMonth = daysArray - }, - - datePickerFormatDate(date, format = null) { - let formattedDay = this.datePickerDays[date.getUTCDay()] - let formattedDate = ('0' + date.getUTCDate()).slice(-2) // appends 0 (zero) in single digit date - let formattedMonth = this.datePickerMonthNames[date.getUTCMonth()] - let formattedMonthShortName = this.datePickerMonthNames[date.getUTCMonth()].substring(0, 3) - let formattedMonthInNumber = ('0' + (parseInt(date.getUTCMonth()) + 1)).slice(-2) - let formattedYear = date.getUTCFullYear() - - if (format && format === 'd M, Y') { - return `${formattedDate} ${formattedMonthShortName}, ${formattedYear}` - } - - if (format && format === 'MM-DD-YYYY') { - return `${formattedMonthInNumber}-${formattedDate}-${formattedYear}` - } - - if (format && format === 'DD-MM-YYYY') { - return `${formattedDate}-${formattedMonthInNumber}-${formattedYear}` - } - - if (format && format === 'YYYY-MM-DD') { - return `${formattedYear}-${formattedMonthInNumber}-${formattedDate}` - } - - if (format && format === 'D d M, Y') { - return `${formattedDay} ${formattedDate} ${formattedMonthShortName} ${formattedYear}` - } - - return `${formattedMonth} ${formattedDate}, ${formattedYear}` - }, - - init() { - let currentDate = new Date() - - if (this.datePickerValue) { - currentDate = new Date(Date.parse(this.datePickerValue)) - } - - this.datePickerMonth = currentDate.getUTCMonth() - this.datePickerYear = currentDate.getUTCFullYear() - this.datePickerDay = currentDate.getUTCDay() - this.datePickerValue = this.datePickerFormatDate(currentDate, this.datePickerFormat) - this.datePickerCalculateDays() - - this.$watch('datePickerValue', () => { - this.datePickerRealValue = new Date(Date.UTC(this.datePickerYear, this.datePickerMonth, this.datePickerDay)) - }) - }, -}) diff --git a/resources/js/components/internationalNumber.js b/resources/js/components/internationalNumber.js deleted file mode 100644 index c367a337..00000000 --- a/resources/js/components/internationalNumber.js +++ /dev/null @@ -1,26 +0,0 @@ -import intlTelInput from 'intl-tel-input' - -export default (element) => ({ - input: element, // '#myID' selector css - - init() { - const phoneNumber = document.querySelector(this.input) - let iti = intlTelInput(phoneNumber, { - nationalMode: true, - geoIpLookup: function (success, failure) { - fetch('https://ipinfo.io').then((response) => { - let countryCode = response && response.country ? response.country : 'CM' - success(countryCode) - }) - }, - utilsScript: 'https://unpkg.com/intl-tel-input@17.0.3/build/js/utils.js', - }) - let handleChange = () => { - if (iti.isValidNumber()) { - phoneNumber.value = iti.getNumber() - } - } - phoneNumber.addEventListener('change', handleChange) - phoneNumber.addEventListener('keyup', handleChange) - }, -}) diff --git a/resources/js/elements/TimeAgo.js b/resources/js/elements/TimeAgo.js deleted file mode 100644 index 86be10e9..00000000 --- a/resources/js/elements/TimeAgo.js +++ /dev/null @@ -1,102 +0,0 @@ -const terms = [ - { - time: 45, - divide: 60, - text: "moins d'une minute", - }, - { - time: 90, - divide: 60, - text: 'environ une minute', - }, - { - time: 45 * 60, - divide: 60, - text: '%d minutes', - }, - { - time: 90 * 60, - divide: 60 * 60, - text: 'environ une heure', - }, - { - time: 24 * 60 * 60, - divide: 60 * 60, - text: '%d heures', - }, - { - time: 42 * 60 * 60, - divide: 24 * 60 * 60, - text: 'environ un jour', - }, - { - time: 30 * 24 * 60 * 60, - divide: 24 * 60 * 60, - text: '%d jours', - }, - { - time: 45 * 24 * 60 * 60, - divide: 24 * 60 * 60 * 30, - text: 'environ un mois', - }, - { - time: 365 * 24 * 60 * 60, - divide: 24 * 60 * 60 * 30, - text: '%d mois', - }, - { - time: 365 * 1.5 * 24 * 60 * 60, - divide: 24 * 60 * 60 * 365, - text: 'environ un an', - }, - { - time: Infinity, - divide: 24 * 60 * 60 * 365, - text: '%d ans', - }, -] - -/** - * Custom element permettant d'afficher une date de manière relative - * - * @property {number} timer - */ -export class TimeAgo extends HTMLElement { - connectedCallback() { - const timestamp = parseInt(this.getAttribute('time'), 10) * 1000 - const date = new Date(timestamp) - this.updateText(date) - } - - disconnectedCallback() { - window.clearTimeout(this.timer) - } - - updateText(date) { - const seconds = (new Date().getTime() - date.getTime()) / 1000 - let term = null - const prefix = this.getAttribute('prefix') - for (term of terms) { - if (Math.abs(seconds) < term.time) { - break - } - } - if (seconds >= 0) { - this.innerHTML = `${prefix || 'il y a'} ${term.text.replace('%d', Math.round(seconds / term.divide))}` - } else { - this.innerHTML = `${prefix || 'dans'} ${term.text.replace('%d', Math.round(Math.abs(seconds) / term.divide))}` - } - let nextTick = Math.abs(seconds) % term.divide - if (nextTick === 0) { - nextTick = term.divide - } - if (nextTick > 2147482) { - return - } - this.timer = window.setTimeout(() => { - window.requestAnimationFrame(() => { - this.updateText(date) - }) - }, 1000 * nextTick) - } -} diff --git a/resources/js/elements/TimeCountdown.js b/resources/js/elements/TimeCountdown.js deleted file mode 100644 index a6a41163..00000000 --- a/resources/js/elements/TimeCountdown.js +++ /dev/null @@ -1,47 +0,0 @@ -const DAY = 1000 * 60 * 60 * 24 -const HOUR = 1000 * 60 * 60 -const MINUTE = 1000 * 60 - -export class TimeCountdown extends HTMLElement { - connectedCallback() { - const timestamp = parseInt(this.getAttribute('time'), 10) * 1000 - const date = new Date(timestamp) - this.updateText(date) - } - - disconnectedCallback() { - window.clearTimeout(this.timer) - } - - updateText(date) { - const now = new Date().getTime() - const distance = date - now - const days = Math.floor(distance / DAY) - const hours = Math.floor((distance % DAY) / HOUR) - const minutes = Math.floor((distance % HOUR) / MINUTE) - const seconds = Math.floor((distance % MINUTE) / 1000) - if (distance < 0) { - this.innerText = '' - return '' - } - let timeInterval = 1000 - if (days > 0) { - this.innerText = `${days}j ${hours}h` - timeInterval = HOUR - } else if (hours > 0) { - this.innerText = `${hours}h ${minutes}m` - timeInterval = MINUTE - } else { - this.innerText = `${minutes}m ${seconds}s` - } - if (distance > 0) { - this.timer = window.setTimeout(() => { - if (window.requestAnimationFrame) { - window.requestAnimationFrame(() => this.updateText(date)) - } else { - this.updateText(date) - } - }, timeInterval) - } - } -} diff --git a/resources/js/elements/index.js b/resources/js/elements/index.js deleted file mode 100644 index 081acd10..00000000 --- a/resources/js/elements/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { TimeAgo } from './TimeAgo' -import { TimeCountdown } from './TimeCountdown' - -customElements.define('time-ago', TimeAgo) -customElements.define('time-countdown', TimeCountdown) diff --git a/resources/js/utils/dom.js b/resources/js/utils/dom.js deleted file mode 100644 index 55fc1c8a..00000000 --- a/resources/js/utils/dom.js +++ /dev/null @@ -1,83 +0,0 @@ -import htm from 'htm/mini' - -/** - * Trouve la position de l'élément par rapport au haut de la page de manière recursive - * - * @param {HTMLElement} element - * @param {HTMLElement|null} parent - */ -export function offsetTop(element, parent = null) { - let top = element.offsetTop - while ((element = element.offsetParent)) { - if (parent === element) { - return top - } - top += element.offsetTop - } - return top -} - -/** - * Crée un élément HTML - * - * Cette fonction ne couvre que les besoins de l'application, jsx-dom pourrait remplacer cette fonction - * - * @param {string} tagName - * @param {object} attributes - * @param {...HTMLElement|string} children - * @return HTMLElement - */ -export function createElement(tagName, attributes = {}, ...children) { - if (typeof tagName === 'function') { - return tagName(attributes) - } - - const svgTags = ['svg', 'use', 'path', 'circle', 'g'] - // On construit l'élément - const e = !svgTags.includes(tagName) - ? document.createElement(tagName) - : document.createElementNS('http://www.w3.org/2000/svg', tagName) - - // On lui associe les bons attributs - for (const k of Object.keys(attributes || {})) { - if (typeof attributes[k] === 'function' && k.startsWith('on')) { - e.addEventListener(k.substr(2).toLowerCase(), attributes[k]) - } else if (k === 'xlink:href') { - e.setAttributeNS('http://www.w3.org/1999/xlink', 'href', attributes[k]) - } else { - e.setAttribute(k, attributes[k]) - } - } - - // On aplatit les enfants - children = children.reduce((acc, child) => { - return Array.isArray(child) ? [...acc, ...child] : [...acc, child] - }, []) - - // On ajoute les enfants à l'élément - for (const child of children) { - if (typeof child === 'string' || typeof child === 'number') { - e.appendChild(document.createTextNode(child)) - } else if (child instanceof HTMLElement || child instanceof SVGElement) { - e.appendChild(child) - } else { - console.error("Impossible d'ajouter l'élément", child, typeof child) - } - } - return e -} - -/** - * CreateElement version Tagged templates - * @type {(strings: TemplateStringsArray, ...values: any[]) => (HTMLElement[] | HTMLElement)} - */ -export const html = htm.bind(createElement) - -/** - * Génère une classe à partir de différentes variables - * - * @param {...string|null} classnames - */ -export function classNames(...classnames) { - return classnames.filter((classname) => classname !== null && classname !== false).join(' ') -} diff --git a/resources/js/utils/header.js b/resources/js/utils/header.js deleted file mode 100644 index 8d90f490..00000000 --- a/resources/js/utils/header.js +++ /dev/null @@ -1,82 +0,0 @@ -import { throttle } from './timers' - -let $header = document.querySelector('.header') -let currentTop = 0 -let previousTop = 0 -let scrolling = false -const scrollDelta = 20 -let scrollOffset = $header ? $header.offsetHeight : 0 - -// Les différents états possibles du header -const FIXED = 0 -const HIDDEN = 1 -const DEFAULT = 2 -let state = DEFAULT - -/** - * Fonction de changement d'état du header - * - * @param {number} newState - */ -function setState(newState) { - // Le header n'a pas changé d'état - if (newState === state) { - return - } - - if (newState === HIDDEN) { - $header.classList.add('is-hidden') - } else if (newState === FIXED) { - $header.classList.remove('is-hidden') - $header.classList.add('is-fixed') - } else if (newState === DEFAULT) { - $header.classList.remove('is-hidden') - $header.classList.remove('is-fixed') - } - - state = newState -} - -const autoHideHeader = function () { - if (!$header) { - return - } - currentTop = document.documentElement.scrollTop - // Opacité sur le header - if (currentTop > $header.offsetHeight) { - if (currentTop - previousTop > scrollDelta && currentTop > scrollOffset) { - setState(HIDDEN) - } else if (previousTop - currentTop > scrollDelta) { - setState(FIXED) - } - } else { - setState(DEFAULT) - } - - // Masquage / affichage - if (previousTop - currentTop > scrollDelta) { - $header.classList.remove('is-hidden') - } else if (currentTop - previousTop > scrollDelta && currentTop > scrollOffset) { - $header.classList.add('is-hidden') - } - - previousTop = currentTop - scrolling = false -} - -/** - * Enregistre le comportement du header (fixed au scroll) - * @return {function(): void} - */ -export function registerHeader() { - const scrollListener = throttle(() => { - if (!scrolling) { - scrolling = true - window.requestAnimationFrame(autoHideHeader) - } - }, 100) - window.addEventListener('scroll', scrollListener) - return () => { - window.removeEventListener('scroll', scrollListener) - } -} diff --git a/resources/js/utils/helpers.js b/resources/js/utils/helpers.js index 8a5d9811..ddb2e2f8 100644 --- a/resources/js/utils/helpers.js +++ b/resources/js/utils/helpers.js @@ -1,15 +1,3 @@ -import hljs from 'highlight.js' - -window.highlightCode = (element) => { - element.querySelectorAll('pre code').forEach((block) => { - hljs.highlightBlock(block) - }) -} - -window.formatMoney = (amount) => { - return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'XAF' }).format(amount) -} - const share = function () { function popupCenter(url, title, width, height) { let popupWidth = width || 640 diff --git a/resources/js/utils/timers.js b/resources/js/utils/timers.js deleted file mode 100644 index 608bd578..00000000 --- a/resources/js/utils/timers.js +++ /dev/null @@ -1,19 +0,0 @@ -export function throttle(callback, delay) { - let last - let timer - return function () { - let context = this - let now = +new Date() - let args = arguments - if (last && now < last + delay) { - clearTimeout(timer) - timer = setTimeout(function () { - last = now - callback.apply(context, args) - }, delay) - } else { - last = now - callback.apply(context, args) - } - } -} diff --git a/resources/js/utils/window.js b/resources/js/utils/window.js deleted file mode 100644 index 4bf8a998..00000000 --- a/resources/js/utils/window.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Renvoie la hauteur de la fenêtre - * - * @return {number} - */ -export function windowHeight() { - return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight -} - -const uuid = new Date().getTime().toString() -if (localStorage) { - localStorage.setItem('windowId', uuid) - window.addEventListener('focus', function () { - localStorage.setItem('windowId', uuid) - }) -} - -/** - * Renvoie true si la fenêtre est active ou si elle a été la dernière fenêtre active - */ -export function isActiveWindow() { - if (localStorage) { - return uuid === localStorage.getItem('windowId') - } else { - return true - } -} diff --git a/resources/views/components/articles/card.blade.php b/resources/views/components/articles/card.blade.php index 7056f340..aaa21a05 100644 --- a/resources/views/components/articles/card.blade.php +++ b/resources/views/components/articles/card.blade.php @@ -14,7 +14,7 @@ @endphp @if(! $isSummary) -
+
@endif -
$isSummary])> -
-
! $isSummary, - 'flex-col justify-between gap-2' => $isSummary, - ])> - @if ($article->tags->isNotEmpty()) -
- @foreach ($article->tags as $tag) - - @endforeach -
- @endif +
$isSummary])> +
! $isSummary, + 'flex-col justify-between gap-2' => $isSummary, + ])> + @if ($article->tags->isNotEmpty()) +
+ @foreach ($article->tags as $tag) + + @endforeach +
+ @endif - -
- -

- {{ $article->title }} -

- @if ($iconLink) -
-

- {!! $article->excerpt(150) !!} -

+
+ +

+ {{ $article->title }} +

+ + @if ($iconLink) +
+

+ {!! $article->excerpt(150) !!} +

diff --git a/resources/views/components/discussions/overview.blade.php b/resources/views/components/discussions/overview.blade.php index 2f07d913..4ab2db53 100644 --- a/resources/views/components/discussions/overview.blade.php +++ b/resources/views/components/discussions/overview.blade.php @@ -35,7 +35,7 @@ class="size-6" {{ __('global.posted_by') }}
{{ $discussion->user->name }} diff --git a/resources/views/components/forum/thread.blade.php b/resources/views/components/forum/thread.blade.php index 3224161e..2f1e77f5 100644 --- a/resources/views/components/forum/thread.blade.php +++ b/resources/views/components/forum/thread.blade.php @@ -29,13 +29,13 @@ class="inline-flex items-center rounded-xl gap-1 px-2 py-0.5 font-medium bg-prim
- + {{ '@' . $thread->user->username }} - + {{ __('global.ask') }}
@if($tag->description)

diff --git a/resources/views/livewire/pages/discussions/index.blade.php b/resources/views/livewire/pages/discussions/index.blade.php index b50b9690..c8364176 100644 --- a/resources/views/livewire/pages/discussions/index.blade.php +++ b/resources/views/livewire/pages/discussions/index.blade.php @@ -29,6 +29,10 @@ class="bg-white rounded-xl ring-1 ring-gray-200/60 dark:bg-gray-800 dark:ring-wh

+ + + +