BloomFeed Upgrade v0.21
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<div class="form-status">{{ session('status') }}</div>
|
||||
@endif
|
||||
|
||||
<a href="{{ url()->previous(route('dashboard')) }}" class="back-link">← Retour aux articles</a>
|
||||
<a href="{{ url()->previous(route('dashboard')) }}" class="back-link">← {{ __('app.back_to_articles') }}</a>
|
||||
|
||||
<article class="card reader-article">
|
||||
<h1 class="reader-title">{{ $article->title }}</h1>
|
||||
@@ -15,15 +15,15 @@
|
||||
@if ($article->published_at)
|
||||
<span>· {{ $article->published_at->diffForHumans() }}</span>
|
||||
@endif
|
||||
<a href="{{ $article->link }}" target="_blank" rel="noopener" class="reader-source-link">Voir la source originale ↗</a>
|
||||
<a href="{{ $article->link }}" target="_blank" rel="noopener" class="reader-source-link">{{ __('app.view_original') }} ↗</a>
|
||||
</div>
|
||||
|
||||
@if ($article->full_content_error)
|
||||
<div class="reader-error">
|
||||
<p>Impossible de récupérer le contenu complet de cet article ({{ $article->full_content_error }}).</p>
|
||||
<p>{{ __('app.fetch_error', ['error' => $article->full_content_error]) }}</p>
|
||||
<form method="POST" action="{{ route('articles.retry', $article) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-ghost btn-sm">Réessayer</button>
|
||||
<button type="submit" class="btn btn-ghost btn-sm">{{ __('app.retry') }}</button>
|
||||
</form>
|
||||
@if ($article->excerpt)
|
||||
<p class="mt-2">{{ $article->excerpt }}</p>
|
||||
@@ -32,7 +32,7 @@
|
||||
@elseif ($contentHtml)
|
||||
<div class="prose">{!! $contentHtml !!}</div>
|
||||
@else
|
||||
<p class="text-muted">Récupération du contenu en cours…</p>
|
||||
<p class="text-muted">{{ __('app.fetching') }}</p>
|
||||
@endif
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@extends('layouts.guest', ['title' => 'Connexion — BloomFeed'])
|
||||
@extends('layouts.guest', ['title' => __('app.login_title').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<div class="auth-card">
|
||||
@@ -6,8 +6,8 @@
|
||||
<img src="{{ asset('images/logo.png') }}" alt="BloomFeed" class="brand-logo brand-logo-light">
|
||||
<img src="{{ asset('images/logo-dark.png') }}" alt="BloomFeed" class="brand-logo brand-logo-dark">
|
||||
</div>
|
||||
<h1>Connexion</h1>
|
||||
<p class="subtitle">Accédez à vos flux et à votre lecture approfondie</p>
|
||||
<h1>{{ __('app.login_title') }}</h1>
|
||||
<p class="subtitle">{{ __('app.login_subtitle') }}</p>
|
||||
|
||||
@if (session('status'))
|
||||
<div class="form-status">{{ session('status') }}</div>
|
||||
@@ -20,20 +20,20 @@
|
||||
<form method="POST" action="{{ route('login') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="email">Adresse e-mail</label>
|
||||
<label for="email">{{ __('app.email') }}</label>
|
||||
<input type="email" name="email" id="email" class="form-input" value="{{ old('email') }}" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Mot de passe</label>
|
||||
<label for="password">{{ __('app.password') }}</label>
|
||||
<input type="password" name="password" id="password" class="form-input" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Se connecter</button>
|
||||
<button type="submit" class="btn btn-primary btn-block">{{ __('app.login') }}</button>
|
||||
</form>
|
||||
|
||||
@unless (\App\Models\User::query()->exists())
|
||||
@if (! \App\Models\User::query()->exists() || \App\Models\Setting::registrationsOpen())
|
||||
<div class="switch-link">
|
||||
Pas encore de compte ? <a href="{{ route('register') }}">Créer un compte</a>
|
||||
{{ __('app.no_account_yet') }} <a href="{{ route('register') }}">{{ __('app.create_account') }}</a>
|
||||
</div>
|
||||
@endunless
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@extends('layouts.guest', ['title' => 'Créer un compte — BloomFeed'])
|
||||
@extends('layouts.guest', ['title' => __('app.create_account').' — BloomFeed'])
|
||||
|
||||
@php($isFirstUser = ! \App\Models\User::query()->exists())
|
||||
|
||||
@section('content')
|
||||
<div class="auth-card">
|
||||
@@ -6,8 +8,8 @@
|
||||
<img src="{{ asset('images/logo.png') }}" alt="BloomFeed" class="brand-logo brand-logo-light">
|
||||
<img src="{{ asset('images/logo-dark.png') }}" alt="BloomFeed" class="brand-logo brand-logo-dark">
|
||||
</div>
|
||||
<h1>Configurer votre instance</h1>
|
||||
<p class="subtitle">Ce compte sera le seul compte propriétaire de cette instance BloomFeed</p>
|
||||
<h1>{{ $isFirstUser ? __('app.register_owner_title') : __('app.register_title') }}</h1>
|
||||
<p class="subtitle">{{ $isFirstUser ? __('app.register_owner_subtitle') : __('app.register_subtitle') }}</p>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="form-error mt-1">{{ $errors->first() }}</div>
|
||||
@@ -16,26 +18,26 @@
|
||||
<form method="POST" action="{{ route('register') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="name">Nom</label>
|
||||
<label for="name">{{ __('app.name') }}</label>
|
||||
<input type="text" name="name" id="name" class="form-input" value="{{ old('name') }}" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Adresse e-mail</label>
|
||||
<label for="email">{{ __('app.email') }}</label>
|
||||
<input type="email" name="email" id="email" class="form-input" value="{{ old('email') }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Mot de passe</label>
|
||||
<label for="password">{{ __('app.password') }}</label>
|
||||
<input type="password" name="password" id="password" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_confirmation">Confirmer le mot de passe</label>
|
||||
<label for="password_confirmation">{{ __('app.confirm_password') }}</label>
|
||||
<input type="password" name="password_confirmation" id="password_confirmation" class="form-input" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Créer mon compte</button>
|
||||
<button type="submit" class="btn btn-primary btn-block">{{ __('app.register_button') }}</button>
|
||||
</form>
|
||||
|
||||
<div class="switch-link">
|
||||
Déjà un compte ? <a href="{{ route('login') }}">Se connecter</a>
|
||||
{{ __('app.already_account') }} <a href="{{ route('login') }}">{{ __('app.login') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@extends('layouts.app', ['title' => 'Catalogue — BloomFeed'])
|
||||
@extends('layouts.app', ['title' => __('app.catalog').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<div class="main-column" style="width:100%;">
|
||||
@@ -8,22 +8,37 @@
|
||||
|
||||
<div class="market-header">
|
||||
<div>
|
||||
<h2 style="margin:0;">Catalogue de flux</h2>
|
||||
<h2 style="margin:0;">{{ __('app.catalog_title') }}</h2>
|
||||
<p class="text-muted text-sm" style="margin:4px 0 0;">
|
||||
Une sélection de flux RSS livrée avec BloomFeed — ajoutez-les à votre collection en un clic.
|
||||
{{ __('app.catalog_lede') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($feeds->isEmpty())
|
||||
<div class="card empty-state">
|
||||
Le catalogue est vide pour le moment.
|
||||
{{ __('app.catalog_empty') }}
|
||||
</div>
|
||||
@else
|
||||
<input type="search" id="market-search" class="form-input market-search" placeholder="Rechercher un flux… (nom, description, catégorie)">
|
||||
@php($languageNames = [
|
||||
'fr' => 'Français', 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch',
|
||||
'it' => 'Italiano', 'pt' => 'Português', 'nl' => 'Nederlands', 'ja' => '日本語',
|
||||
'ru' => 'Русский', 'pl' => 'Polski', 'sv' => 'Svenska',
|
||||
])
|
||||
@php($languages = $feeds->flatten(1)->pluck('lang')->unique()->sort()->values())
|
||||
|
||||
<div class="market-toolbar">
|
||||
<input type="search" id="market-search" class="form-input market-search" placeholder="{{ __('app.search_placeholder') }}">
|
||||
<select id="market-lang" class="form-input market-lang-select">
|
||||
<option value="">{{ __('app.all_languages') }}</option>
|
||||
@foreach ($languages as $lang)
|
||||
<option value="{{ $lang }}">{{ $languageNames[$lang] ?? strtoupper($lang) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="market-chips" id="market-chips">
|
||||
<button type="button" class="chip active" data-category="">Tout</button>
|
||||
<button type="button" class="chip active" data-category="">{{ __('app.all') }}</button>
|
||||
@foreach ($feeds->keys() as $category)
|
||||
<button type="button" class="chip" data-category="{{ $category }}">{{ $category }}</button>
|
||||
@endforeach
|
||||
@@ -32,7 +47,7 @@
|
||||
<div class="market-grid" id="market-grid">
|
||||
@foreach ($feeds as $category => $items)
|
||||
@foreach ($items as $item)
|
||||
<div class="market-card" data-category="{{ $category }}" data-search="{{ Str::lower($item['title'].' '.$item['description'].' '.$category) }}">
|
||||
<div class="market-card" data-category="{{ $category }}" data-lang="{{ $item['lang'] }}" data-search="{{ Str::lower($item['title'].' '.$item['description'].' '.$category) }}">
|
||||
<div class="market-card-head">
|
||||
<span class="market-card-title">{{ $item['title'] }}</span>
|
||||
<span class="chip chip-static">{{ $category }}</span>
|
||||
@@ -41,13 +56,13 @@
|
||||
<div class="market-card-foot">
|
||||
<span class="market-lang">{{ strtoupper($item['lang']) }}</span>
|
||||
@if (in_array($item['url'], $subscribedUrls))
|
||||
<span class="market-added">✓ Ajouté</span>
|
||||
<span class="market-added">✓ {{ __('app.added') }}</span>
|
||||
@else
|
||||
<form method="POST" action="{{ route('feeds.store') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="url" value="{{ $item['url'] }}">
|
||||
<input type="hidden" name="title" value="{{ $item['title'] }}">
|
||||
<button type="submit" class="btn btn-primary btn-sm">+ Ajouter</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm">+ {{ __('app.add') }}</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@@ -56,7 +71,7 @@
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="card empty-state" id="market-empty" hidden>Aucun flux ne correspond à votre recherche.</div>
|
||||
<div class="card empty-state" id="market-empty" hidden>{{ __('app.no_match') }}</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@extends('layouts.app', ['title' => 'Articles — BloomFeed'])
|
||||
@extends('layouts.app', ['title' => __('app.articles').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<div class="sidebar">
|
||||
@@ -11,11 +11,11 @@
|
||||
@endif
|
||||
|
||||
<div class="flex-between">
|
||||
<h2 style="margin:0;">Derniers articles</h2>
|
||||
<h2 style="margin:0;">{{ __('app.latest_articles') }}</h2>
|
||||
<div class="flex" style="gap:8px;">
|
||||
<a href="{{ route('dashboard', array_filter(['feed' => request('feed'), 'unread' => request('unread') ? null : 1])) }}"
|
||||
class="btn btn-ghost btn-sm">
|
||||
{{ request('unread') ? 'Tout afficher' : 'Non lus uniquement' }}
|
||||
{{ request('unread') ? __('app.show_all') : __('app.unread_only') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,9 +25,9 @@
|
||||
@empty
|
||||
<div class="card empty-state">
|
||||
@if ($feeds->isEmpty())
|
||||
Ajoutez votre premier flux RSS depuis la barre latérale pour commencer.
|
||||
{{ __('app.empty_no_feeds') }}
|
||||
@else
|
||||
Aucun article pour le moment. Essayez d'actualiser vos flux.
|
||||
{{ __('app.empty_no_articles') }}
|
||||
@endif
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@extends('layouts.app', ['title' => 'Mes flux — BloomFeed'])
|
||||
@extends('layouts.app', ['title' => __('app.my_feeds').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<div class="main-column" style="width:100%;">
|
||||
@@ -7,19 +7,19 @@
|
||||
@endif
|
||||
|
||||
<div class="card">
|
||||
<h2>Ajouter un flux</h2>
|
||||
<h2>{{ __('app.add_feed') }}</h2>
|
||||
<form method="POST" action="{{ route('feeds.store') }}" class="inline-form">
|
||||
@csrf
|
||||
<input type="url" name="url" class="form-input" placeholder="https://exemple.com/rss.xml" required>
|
||||
<input type="text" name="title" class="form-input" placeholder="Titre personnalisé (optionnel)" style="max-width:220px;">
|
||||
<button type="submit" class="btn btn-primary">Ajouter</button>
|
||||
<input type="url" name="url" class="form-input" placeholder="{{ __('app.feed_url_placeholder') }}" required>
|
||||
<input type="text" name="title" class="form-input" placeholder="{{ __('app.custom_title_optional') }}" style="max-width:220px;">
|
||||
<button type="submit" class="btn btn-primary">{{ __('app.add') }}</button>
|
||||
</form>
|
||||
@error('url')
|
||||
<div class="form-error mt-1">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<h2 class="mt-3">Mes flux</h2>
|
||||
<h2 class="mt-3">{{ __('app.my_feeds') }}</h2>
|
||||
|
||||
@forelse ($feeds as $feed)
|
||||
<div class="card flex-between">
|
||||
@@ -27,9 +27,9 @@
|
||||
<div style="font-weight:600;">{{ $feed->displayTitle() }}</div>
|
||||
<div class="text-sm text-muted">{{ $feed->url }}</div>
|
||||
<div class="text-sm text-muted mt-1">
|
||||
{{ $feed->articles_count }} article(s)
|
||||
{{ __('app.articles_count', ['count' => $feed->articles_count]) }}
|
||||
@if ($feed->last_fetched_at)
|
||||
· actualisé {{ $feed->last_fetched_at->diffForHumans() }}
|
||||
· {{ __('app.refreshed_ago', ['time' => $feed->last_fetched_at->diffForHumans()]) }}
|
||||
@endif
|
||||
@if ($feed->last_fetch_status && $feed->last_fetch_status !== 'ok')
|
||||
· <span style="color:var(--danger);">{{ $feed->last_fetch_status }}</span>
|
||||
@@ -39,17 +39,17 @@
|
||||
<div class="flex" style="gap:8px;">
|
||||
<form method="POST" action="{{ route('feeds.refresh', $feed) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-ghost btn-sm">Actualiser</button>
|
||||
<button type="submit" class="btn btn-ghost btn-sm">{{ __('app.refresh') }}</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('feeds.destroy', $feed) }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger btn-sm" data-confirm="Supprimer ce flux et tous ses articles ?">Supprimer</button>
|
||||
<button type="submit" class="btn btn-danger btn-sm" data-confirm="{{ __('app.confirm_delete_feed') }}">{{ __('app.delete') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="card empty-state">Vous n'avez pas encore ajouté de flux.</div>
|
||||
<div class="card empty-state">{{ __('app.no_feeds_yet') }}</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
@extends('layouts.marketing', ['title' => 'BloomFeed — votre agrégateur RSS auto-hébergé'])
|
||||
@extends('layouts.marketing', ['title' => 'BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<section class="hero">
|
||||
<div class="hero-inner">
|
||||
<span class="eyebrow">Auto-hébergé · Vie privée</span>
|
||||
<h1>Vos flux RSS, lus en profondeur, sans jamais quitter BloomFeed.</h1>
|
||||
<p class="hero-lede">
|
||||
BloomFeed centralise vos flux préférés et en extrait le contenu complet à la demande,
|
||||
directement sur votre propre serveur. Aucune donnée envoyée à des tiers.
|
||||
</p>
|
||||
<span class="eyebrow">{{ __('app.hero_eyebrow') }}</span>
|
||||
<h1>{{ __('app.hero_title') }}</h1>
|
||||
<p class="hero-lede">{{ __('app.hero_lede') }}</p>
|
||||
<div class="hero-cta">
|
||||
@if ($hasOwner)
|
||||
<a href="{{ route('login') }}" class="btn btn-primary btn-lg">Se connecter</a>
|
||||
@if ($hasOwner && ! \App\Models\Setting::registrationsOpen())
|
||||
<a href="{{ route('login') }}" class="btn btn-primary btn-lg">{{ __('app.login') }}</a>
|
||||
@else
|
||||
<a href="{{ route('register') }}" class="btn btn-primary btn-lg">Créer un compte</a>
|
||||
<a href="{{ route('register') }}" class="btn btn-primary btn-lg">{{ __('app.create_account') }}</a>
|
||||
@endif
|
||||
<a href="{{ route('self-hosting') }}" class="btn btn-ghost btn-lg">Comment l'auto-héberger ?</a>
|
||||
<a href="{{ route('self-hosting') }}" class="btn btn-ghost btn-lg">{{ __('app.how_to_self_host') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -23,18 +20,18 @@
|
||||
<section class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📰</div>
|
||||
<h3>Flux personnalisés</h3>
|
||||
<p>Ajoutez n'importe quel flux RSS ou Atom, organisez-les et suivez vos non-lus en un coup d'œil.</p>
|
||||
<h3>{{ __('app.feature_feeds_title') }}</h3>
|
||||
<p>{{ __('app.feature_feeds_text') }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📖</div>
|
||||
<h3>Lecture approfondie intégrée</h3>
|
||||
<p>BloomFeed récupère la page source, en extrait le contenu principal et l'affiche proprement — plus besoin d'ouvrir dix onglets.</p>
|
||||
<h3>{{ __('app.feature_reader_title') }}</h3>
|
||||
<p>{{ __('app.feature_reader_text') }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🔒</div>
|
||||
<h3>Chez vous, pour vous</h3>
|
||||
<p>Une instance BloomFeed = un propriétaire. Pas d'inscriptions publiques, pas de tracking, votre serveur, vos données.</p>
|
||||
<h3>{{ __('app.feature_home_title') }}</h3>
|
||||
<p>{{ __('app.feature_home_text') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -20,6 +20,10 @@
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="guest-topbar">
|
||||
@include('partials.locale-selector')
|
||||
<button type="button" class="theme-toggle" data-theme-toggle title="{{ __('app.change_theme') }}">◐</button>
|
||||
</div>
|
||||
<div class="centered-shell">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{{ $title ?? 'BloomFeed' }}</title>
|
||||
<meta name="description" content="BloomFeed est un agrégateur RSS personnel et auto-hébergé, avec lecture approfondie intégrée.">
|
||||
<meta name="description" content="{{ __('app.meta_description') }}">
|
||||
<link rel="icon" href="{{ asset('images/icon.png') }}">
|
||||
<link rel="apple-touch-icon" href="{{ asset('images/icon.png') }}">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
@@ -27,12 +27,13 @@
|
||||
<img src="{{ asset('images/logo-dark.png') }}" alt="BloomFeed" class="brand-logo brand-logo-dark">
|
||||
</a>
|
||||
<div class="navbar-links">
|
||||
<a href="{{ route('self-hosting') }}" class="{{ request()->routeIs('self-hosting') ? 'active' : '' }}">Self-hosting</a>
|
||||
<button type="button" class="theme-toggle" data-theme-toggle title="Changer de thème">◐</button>
|
||||
@if (\App\Models\User::query()->exists())
|
||||
<a href="{{ route('login') }}" class="btn btn-primary btn-sm">Se connecter</a>
|
||||
<a href="{{ route('self-hosting') }}" class="{{ request()->routeIs('self-hosting') ? 'active' : '' }}">{{ __('app.self_hosting') }}</a>
|
||||
@include('partials.locale-selector')
|
||||
<button type="button" class="theme-toggle" data-theme-toggle title="{{ __('app.change_theme') }}">◐</button>
|
||||
@if (\App\Models\User::query()->exists() && ! \App\Models\Setting::registrationsOpen())
|
||||
<a href="{{ route('login') }}" class="btn btn-primary btn-sm">{{ __('app.login') }}</a>
|
||||
@else
|
||||
<a href="{{ route('register') }}" class="btn btn-primary btn-sm">Créer un compte</a>
|
||||
<a href="{{ route('register') }}" class="btn btn-primary btn-sm">{{ __('app.create_account') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</nav>
|
||||
@@ -42,8 +43,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<span>BloomFeed — projet open-source, auto-hébergé.</span>
|
||||
<a href="https://git.eldorianet.work/esteban/BloomFeed-RSS" target="_blank" rel="noopener">Code source</a>
|
||||
<span>{{ __('app.footer_tagline') }}</span>
|
||||
<a href="https://git.eldorianet.work/esteban/BloomFeed-RSS" target="_blank" rel="noopener">{{ __('app.source_code') }}</a>
|
||||
</footer>
|
||||
|
||||
<script src="{{ asset('js/theme-toggle.js') }}" defer></script>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="card">
|
||||
<div class="sidebar-title">Flux</div>
|
||||
<div class="sidebar-title">{{ __('app.feeds_label') }}</div>
|
||||
<ul class="feed-list">
|
||||
<li>
|
||||
<a href="{{ route('dashboard') }}" class="{{ ! request('feed') ? 'active' : '' }}">
|
||||
Tous les articles
|
||||
{{ __('app.all_articles') }}
|
||||
</a>
|
||||
</li>
|
||||
@foreach ($feeds as $feed)
|
||||
@@ -21,18 +21,18 @@
|
||||
<form method="POST" action="{{ route('feeds.store') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="sidebar-url">Ajouter un flux</label>
|
||||
<input type="url" name="url" id="sidebar-url" class="form-input" placeholder="https://exemple.com/rss.xml" required>
|
||||
<label for="sidebar-url">{{ __('app.add_feed') }}</label>
|
||||
<input type="url" name="url" id="sidebar-url" class="form-input" placeholder="{{ __('app.feed_url_placeholder') }}" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-sm">Ajouter</button>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-sm">{{ __('app.add') }}</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('feeds.refreshAll') }}" class="mt-2">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-ghost btn-block btn-sm">Tout actualiser</button>
|
||||
<button type="submit" class="btn btn-ghost btn-block btn-sm">{{ __('app.refresh_all') }}</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ route('feeds.index') }}" class="text-sm text-muted mt-1" style="display:block; text-align:center;">
|
||||
Gérer mes flux
|
||||
{{ __('app.manage_feeds') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<form method="POST" action="{{ route('locale.update') }}" class="locale-form">
|
||||
@csrf
|
||||
<select name="locale" class="locale-select" onchange="this.form.submit()" title="{{ __('app.language') }}">
|
||||
@foreach (['fr' => 'Français', 'en' => 'English', 'es' => 'Español', 'de' => 'Deutsch'] as $code => $label)
|
||||
<option value="{{ $code }}" @selected(app()->getLocale() === $code)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
@@ -4,13 +4,17 @@
|
||||
<img src="{{ asset('images/logo-dark.png') }}" alt="BloomFeed" class="brand-logo brand-logo-dark">
|
||||
</a>
|
||||
<div class="navbar-links">
|
||||
<a href="{{ route('dashboard') }}" class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">Articles</a>
|
||||
<a href="{{ route('catalog') }}" class="{{ request()->routeIs('catalog') ? 'active' : '' }}">Catalogue</a>
|
||||
<a href="{{ route('feeds.index') }}" class="{{ request()->routeIs('feeds.*') ? 'active' : '' }}">Mes flux</a>
|
||||
<button type="button" class="theme-toggle" data-theme-toggle title="Changer de thème">◐</button>
|
||||
<a href="{{ route('dashboard') }}" class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">{{ __('app.articles') }}</a>
|
||||
<a href="{{ route('catalog') }}" class="{{ request()->routeIs('catalog') ? 'active' : '' }}">{{ __('app.catalog') }}</a>
|
||||
<a href="{{ route('feeds.index') }}" class="{{ request()->routeIs('feeds.*') ? 'active' : '' }}">{{ __('app.my_feeds') }}</a>
|
||||
@if (auth()->user()?->is_owner)
|
||||
<a href="{{ route('settings') }}" class="{{ request()->routeIs('settings') ? 'active' : '' }}">{{ __('app.settings') }}</a>
|
||||
@endif
|
||||
@include('partials.locale-selector')
|
||||
<button type="button" class="theme-toggle" data-theme-toggle title="{{ __('app.change_theme') }}">◐</button>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-ghost btn-sm">Déconnexion</button>
|
||||
<button type="submit" class="btn btn-ghost btn-sm">{{ __('app.logout') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,55 +1,60 @@
|
||||
@extends('layouts.marketing', ['title' => 'Auto-héberger BloomFeed'])
|
||||
@extends('layouts.marketing', ['title' => __('app.selfhost_title').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<section class="docs-shell">
|
||||
<h1>Auto-héberger BloomFeed</h1>
|
||||
<p class="hero-lede">
|
||||
BloomFeed est pensé pour être installé sur votre propre serveur, pour vous (ou votre foyer).
|
||||
Une instance = un seul compte propriétaire. Tout tourne dans Docker : l'application
|
||||
et la base de données MariaDB.
|
||||
</p>
|
||||
<h1>{{ __('app.selfhost_title') }}</h1>
|
||||
<p class="hero-lede">{{ __('app.selfhost_intro') }}</p>
|
||||
|
||||
<div class="card mt-3">
|
||||
<h2>Prérequis</h2>
|
||||
<h2>{{ __('app.prereq_title') }}</h2>
|
||||
<ul class="docs-list">
|
||||
<li>Un serveur Linux avec <strong>Docker</strong> et le plugin <strong>Docker Compose</strong>.</li>
|
||||
<li>Le port <strong>7081</strong> libre (modifiable dans <code>docker-compose.yml</code>).</li>
|
||||
<li>Optionnel : un nom de domaine et un reverse proxy si vous voulez du HTTPS.</li>
|
||||
<li>{{ __('app.prereq_docker') }}</li>
|
||||
<li>{{ __('app.prereq_port') }}</li>
|
||||
<li>{{ __('app.prereq_proxy') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<h2>Installation</h2>
|
||||
<h2>{{ __('app.install_title') }}</h2>
|
||||
<ol class="docs-list docs-list-numbered">
|
||||
<li>Clonez le dépôt sur votre serveur.</li>
|
||||
<li>
|
||||
Lancez le script de déploiement :
|
||||
<pre class="code-block">bash scriptsite.sh</pre>
|
||||
Il crée automatiquement un fichier <code>.env</code> (à partir de <code>.env.example</code>) et y génère
|
||||
des secrets aléatoires (<code>APP_KEY</code>, mots de passe de base de données) s'ils sont absents —
|
||||
ils ne sont jamais régénérés lors des déploiements suivants.
|
||||
{{ __('app.install_step_clone') }}
|
||||
<pre class="code-block">git clone https://git.eldorianet.work/esteban/BloomFeed-RSS bloomfeed
|
||||
cd bloomfeed</pre>
|
||||
</li>
|
||||
<li>Le script construit les images et démarre les conteneurs (application, planificateur, MariaDB).</li>
|
||||
<li>Ouvrez <code>http://votre-serveur:7081</code> et créez le compte propriétaire — cette option disparaît ensuite définitivement.</li>
|
||||
<li>
|
||||
{{ __('app.install_step_script') }}
|
||||
<pre class="code-block">bash scriptsite.sh</pre>
|
||||
{{ __('app.install_env_note') }}
|
||||
</li>
|
||||
<li>{{ __('app.install_step_containers') }}</li>
|
||||
<li>{{ __('app.install_step_owner') }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<h2>Mettre à jour</h2>
|
||||
<p>Relancez simplement <code>bash scriptsite.sh</code> : il récupère les derniers changements et reconstruit la stack sans toucher à vos données.</p>
|
||||
<h2>{{ __('app.config_title') }}</h2>
|
||||
<ul class="docs-list">
|
||||
<li>{{ __('app.config_registrations') }}</li>
|
||||
<li>{{ __('app.config_language') }}</li>
|
||||
<li>{{ __('app.config_catalog') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<h2>Désinstaller</h2>
|
||||
<p>
|
||||
Le script <code>uninstall.sh</code> arrête et supprime proprement la stack Docker.
|
||||
Il demande une confirmation explicite avant de supprimer vos données (base de données)
|
||||
et ne supprime jamais le code source sans confirmation séparée.
|
||||
</p>
|
||||
<h2>{{ __('app.update_title') }}</h2>
|
||||
<p>{{ __('app.update_text') }}</p>
|
||||
<pre class="code-block">bash scriptsite.sh</pre>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<h2>Code source</h2>
|
||||
<h2>{{ __('app.uninstall_title') }}</h2>
|
||||
<p>{{ __('app.uninstall_text') }}</p>
|
||||
<pre class="code-block">bash uninstall.sh</pre>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<h2>{{ __('app.source_title') }}</h2>
|
||||
<p>
|
||||
<a href="https://git.eldorianet.work/esteban/BloomFeed-RSS" target="_blank" rel="noopener">git.eldorianet.work/esteban/BloomFeed-RSS</a>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
@extends('layouts.app', ['title' => __('app.settings').' — BloomFeed'])
|
||||
|
||||
@section('content')
|
||||
<div class="main-column" style="width:100%; max-width:720px; margin:0 auto;">
|
||||
@if (session('status'))
|
||||
<div class="form-status">{{ session('status') }}</div>
|
||||
@endif
|
||||
|
||||
<h2 style="margin:0;">{{ __('app.settings_title') }}</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{ __('app.registrations_section') }}</h3>
|
||||
<p class="text-muted text-sm">{{ __('app.registrations_desc') }}</p>
|
||||
<div class="flex-between mt-2">
|
||||
<span class="text-sm" style="font-weight:600; color: {{ $registrationsOpen ? 'var(--green)' : 'var(--text-muted)' }};">
|
||||
{{ $registrationsOpen ? __('app.registrations_state_open') : __('app.registrations_state_closed') }}
|
||||
</span>
|
||||
<form method="POST" action="{{ route('settings.toggleRegistrations') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn {{ $registrationsOpen ? 'btn-danger' : 'btn-primary' }} btn-sm">
|
||||
{{ $registrationsOpen ? __('app.close_registrations') : __('app.open_registrations') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{ __('app.create_user_section') }}</h3>
|
||||
<p class="text-muted text-sm">{{ __('app.create_user_desc') }}</p>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="form-error mt-1">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('settings.createUser') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="new-name">{{ __('app.name') }}</label>
|
||||
<input type="text" name="name" id="new-name" class="form-input" value="{{ old('name') }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new-email">{{ __('app.email') }}</label>
|
||||
<input type="email" name="email" id="new-email" class="form-input" value="{{ old('email') }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new-password">{{ __('app.password') }}</label>
|
||||
<input type="text" name="password" id="new-password" class="form-input" minlength="8" required autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{{ __('app.create_account') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{ __('app.catalog_sources_section') }}</h3>
|
||||
<p class="text-muted text-sm">{{ __('app.catalog_sources_desc') }}</p>
|
||||
|
||||
@error('source_url')
|
||||
<div class="form-error mt-1">{{ $message }}</div>
|
||||
@enderror
|
||||
|
||||
@if (empty($catalogSources))
|
||||
<p class="text-muted text-sm mt-2">{{ __('app.no_sources') }}</p>
|
||||
@else
|
||||
<ul class="feed-list mt-2">
|
||||
@foreach ($catalogSources as $source)
|
||||
<li style="display:flex; align-items:center; justify-content:space-between; gap:12px; padding:8px 10px;">
|
||||
<span class="text-sm" style="overflow-wrap:anywhere;">{{ $source }}</span>
|
||||
<form method="POST" action="{{ route('settings.removeSource') }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<input type="hidden" name="source_url" value="{{ $source }}">
|
||||
<button type="submit" class="btn btn-danger btn-sm">{{ __('app.remove') }}</button>
|
||||
</form>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('settings.addSource') }}" class="mt-2">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="source-url">{{ __('app.source_url_label') }}</label>
|
||||
<input type="url" name="source_url" id="source-url" class="form-input" placeholder="https://git.example.com/user/repo/raw/branch/main/bloomflux.json" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">{{ __('app.add_source') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{ __('app.users_section') }}</h3>
|
||||
<ul class="feed-list mt-1">
|
||||
@foreach ($users as $user)
|
||||
<li style="display:flex; align-items:center; justify-content:space-between; padding:8px 10px;">
|
||||
<span>
|
||||
<strong>{{ $user->name }}</strong>
|
||||
<span class="text-muted text-sm">· {{ $user->email }}</span>
|
||||
</span>
|
||||
@if ($user->is_owner)
|
||||
<span class="pill">{{ __('app.owner_badge') }}</span>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user