Compare commits
6 Commits
2841881a01
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 64c53f412d | |||
| 61fb56fa14 | |||
| 8039a3545d | |||
| fe0476218d | |||
| 1220ce1bb0 | |||
| 4bb2c6cc77 |
@@ -6,4 +6,5 @@ flatpak/.build/
|
|||||||
flatpak/.repo/
|
flatpak/.repo/
|
||||||
flatpak/.flatpak-builder/
|
flatpak/.flatpak-builder/
|
||||||
flatpak/pena.deb
|
flatpak/pena.deb
|
||||||
|
flatpak/icon.png
|
||||||
flatpak/*.flatpak
|
flatpak/*.flatpak
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Vue d'ensemble
|
||||||
|
|
||||||
|
## Qu'est-ce que Pena ?
|
||||||
|
|
||||||
|
Pena est un **lecteur Markdown de bureau**. Son objectif est d'offrir une lecture confortable
|
||||||
|
de documentation écrite en Markdown, qu'il s'agisse d'un fichier unique (un `README.md`) ou
|
||||||
|
d'un ensemble de fichiers organisés en arborescence (un « wiki » de documentation).
|
||||||
|
|
||||||
|
Contrairement à un éditeur, Pena est **focalisé sur la lecture** : il n'édite pas les fichiers,
|
||||||
|
il les affiche joliment, permet de naviguer entre eux, et de personnaliser entièrement
|
||||||
|
l'apparence du rendu.
|
||||||
|
|
||||||
|
## Pour qui ?
|
||||||
|
|
||||||
|
- Toute personne qui consulte régulièrement de la documentation Markdown en local
|
||||||
|
(wikis de projet, notes, documentation technique).
|
||||||
|
- Les utilisateurs qui veulent un rendu plus agréable et configurable que l'aperçu brut
|
||||||
|
d'un éditeur de texte.
|
||||||
|
- Ceux qui maintiennent une documentation multi-fichiers organisée en dossiers et
|
||||||
|
sous-dossiers, et qui veulent naviguer dedans comme dans un wiki.
|
||||||
|
|
||||||
|
## Deux modes d'ouverture
|
||||||
|
|
||||||
|
Pena propose deux façons d'ouvrir du contenu :
|
||||||
|
|
||||||
|
| Mode | Déclencheur | Comportement |
|
||||||
|
|---|---|---|
|
||||||
|
| **Fichier** | « Ouvrir un fichier Markdown » | Ouvre un `.md` isolé. La barre latérale liste tous les `.md` du dossier parent pour faciliter la navigation. |
|
||||||
|
| **Dossier (wiki)** | « Ouvrir un dossier wiki » | Ouvre un dossier complet. Tous les `.md` (y compris dans les sous-dossiers) sont listés en arborescence. Si un fichier `Home.md` existe, il est affiché en premier. |
|
||||||
|
|
||||||
|
## Principes de fonctionnement
|
||||||
|
|
||||||
|
- **Rendu côté Rust** — La conversion Markdown → HTML est faite par le backend Rust
|
||||||
|
(`comrak`), pas par une bibliothèque JS. Cela garantit un rendu rapide et cohérent.
|
||||||
|
- **CommonMark + extensions** — Les tableaux, le texte barré, les liens automatiques et les
|
||||||
|
listes de tâches sont pris en charge.
|
||||||
|
- **Coloration syntaxique** — Les blocs de code sont colorés via `syntect`, avec un thème
|
||||||
|
de coloration intégré (`base16-ocean.dark`).
|
||||||
|
- **Navigation entre documents** — Les liens Markdown relatifs entre fichiers
|
||||||
|
(ex. `[voir](../autre/page.md)`) sont fonctionnels : cliquer dessus charge le document
|
||||||
|
cible dans Pena.
|
||||||
|
- **Tout reste local** — Aucune connexion réseau n'est nécessaire. Les préférences
|
||||||
|
(récents, thème, CSS personnalisé) sont stockées localement dans le navigateur embarqué
|
||||||
|
(localStorage).
|
||||||
|
|
||||||
|
## Ce que Pena ne fait pas (encore)
|
||||||
|
|
||||||
|
- Pas d'édition de fichiers Markdown (lecture seule).
|
||||||
|
- Pas de rechargement automatique à la modification du fichier : le mécanisme de
|
||||||
|
surveillance (« watcher ») existe côté backend mais n'est pas branché côté interface.
|
||||||
|
Voir [Commandes Tauri](../2-technique/2.2-Commandes-Tauri.md#surveillance-de-fichiers-watch).
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Fonctionnalités détaillées](1.2-Fonctionnalites.md)
|
||||||
|
- [Interface](1.3-Interface.md)
|
||||||
|
- [Thèmes et personnalisation](1.4-Themes-et-personnalisation.md)
|
||||||
|
- [Architecture technique](../2-technique/2.1-Architecture.md)
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# Fonctionnalités
|
||||||
|
|
||||||
|
Cette page liste l'ensemble des fonctionnalités visibles par l'utilisateur, regroupées par
|
||||||
|
thème.
|
||||||
|
|
||||||
|
## 1. Ouverture de documents
|
||||||
|
|
||||||
|
### Ouvrir un fichier Markdown
|
||||||
|
- Bouton **« Ouvrir un fichier Markdown »** sur l'écran d'accueil.
|
||||||
|
- Ouvre un sélecteur de fichiers natif, filtré sur l'extension `.md`.
|
||||||
|
- Une fois le fichier ouvert, la barre latérale liste **tous les `.md` du dossier parent**,
|
||||||
|
ce qui permet de basculer entre fichiers voisins.
|
||||||
|
|
||||||
|
### Ouvrir un dossier wiki
|
||||||
|
- Bouton **« Ouvrir un dossier wiki »** sur l'écran d'accueil.
|
||||||
|
- Ouvre un sélecteur de dossier natif.
|
||||||
|
- Tous les fichiers `.md` du dossier **et de ses sous-dossiers** sont listés (récursivement).
|
||||||
|
- Les fichiers et dossiers cachés (commençant par `.`) sont ignorés.
|
||||||
|
- Si un fichier `Home.md` ou `home.md` est présent à la racine, il est affiché en premier ;
|
||||||
|
sinon, c'est le premier fichier par ordre alphabétique.
|
||||||
|
|
||||||
|
## 2. Documents récents
|
||||||
|
|
||||||
|
- L'écran d'accueil affiche une section **« Récents »** (masquée si vide).
|
||||||
|
- Chaque entrée affiche une icône (📁 pour un dossier, 📄 pour un fichier), le nom et le
|
||||||
|
chemin parent.
|
||||||
|
- Un clic réouvre directement le fichier ou le dossier.
|
||||||
|
- Jusqu'à **8 entrées** sont conservées, sans doublon, dans le stockage local (localStorage,
|
||||||
|
clé `pena_recents`).
|
||||||
|
|
||||||
|
## 3. Navigation dans la documentation
|
||||||
|
|
||||||
|
### Barre latérale en arborescence
|
||||||
|
- Affiche la structure complète du dossier ouvert sous forme d'arbre.
|
||||||
|
- Les dossiers sont des sections repliables/dépliables (chevron).
|
||||||
|
- Fichiers et dossiers sont triés par **ordre naturel** : les préfixes numériques
|
||||||
|
sont comparés comme des nombres (`10` vient après `9`, et non après `1`).
|
||||||
|
- Dossiers et fichiers partagent la **même typographie** (taille et graisse) ;
|
||||||
|
seul le chevron ▶ distingue visuellement un dossier.
|
||||||
|
- Si un dossier contient un `home.md`, son nom devient un lien cliquable.
|
||||||
|
- Le document actuellement affiché est **surligné**, et les dossiers parents sont
|
||||||
|
automatiquement dépliés.
|
||||||
|
- Plusieurs niveaux de profondeur sont gérés avec une indentation progressive.
|
||||||
|
|
||||||
|
### Liens entre documents
|
||||||
|
- Les **liens Markdown relatifs** vers d'autres fichiers `.md`
|
||||||
|
(ex. `[autre page](../section/page.md)`) sont fonctionnels : un clic charge le document
|
||||||
|
cible dans Pena, sans quitter l'application. Les chemins relatifs (`../`, `./`) sont
|
||||||
|
résolus correctement.
|
||||||
|
- Les **liens externes** (`http://`, `https://`) s'ouvrent dans le navigateur par défaut
|
||||||
|
(nouvel onglet, avec `rel="noopener noreferrer"`).
|
||||||
|
- Les **ancres internes** (`#section`) restent des ancres de page.
|
||||||
|
|
||||||
|
## 4. Rendu Markdown
|
||||||
|
|
||||||
|
Le rendu prend en charge le standard **CommonMark** ainsi que les extensions suivantes :
|
||||||
|
|
||||||
|
| Extension | Exemple |
|
||||||
|
|---|---|
|
||||||
|
| Tableaux | `\| col A \| col B \|` |
|
||||||
|
| Texte barré | `~~barré~~` |
|
||||||
|
| Liens automatiques | une URL nue devient un lien cliquable |
|
||||||
|
| Listes de tâches | `- [ ] à faire` / `- [x] fait` |
|
||||||
|
|
||||||
|
Autres éléments rendus : titres `h1`–`h6`, code inline, blocs de code, citations
|
||||||
|
(blockquotes), listes, images (largeur adaptée), et liens.
|
||||||
|
|
||||||
|
## 5. Coloration syntaxique du code
|
||||||
|
|
||||||
|
- Les blocs de code délimités par triple backtick sont **colorés syntaxiquement** via
|
||||||
|
`syntect`.
|
||||||
|
- Le thème de coloration intégré est **`base16-ocean.dark`**.
|
||||||
|
- La syntaxe **TypeScript** est fournie en plus des syntaxes par défaut.
|
||||||
|
|
||||||
|
### Bouton « Copier » sur les blocs de code
|
||||||
|
- Chaque bloc de code affiche un bouton **« Copier »** en haut à droite.
|
||||||
|
- Un clic copie le contenu du bloc dans le presse-papiers.
|
||||||
|
- Un retour visuel temporaire (« Copié ! ») confirme l'action.
|
||||||
|
|
||||||
|
## 6. Personnalisation de l'apparence
|
||||||
|
|
||||||
|
Voir la page dédiée [Thèmes et personnalisation](1.4-Themes-et-personnalisation.md). En résumé :
|
||||||
|
|
||||||
|
- **6 thèmes rapides** prêts à l'emploi (Défaut, Mode sombre, Sépia, Grand texte,
|
||||||
|
Accent émeraude, Shell Indigo).
|
||||||
|
- Un **éditeur CSS** intégré, organisé en 4 onglets thématiques, permettant d'écrire son
|
||||||
|
propre CSS appliqué au rendu.
|
||||||
|
- Les choix sont **persistés** localement et réappliqués au démarrage suivant.
|
||||||
|
|
||||||
|
## 7. Fenêtre et barre de titre personnalisée
|
||||||
|
|
||||||
|
- L'application utilise une **barre de titre maison** (la décoration native est désactivée).
|
||||||
|
- Elle affiche le titre du document courant et propose les boutons **réduire**,
|
||||||
|
**agrandir / restaurer** et **fermer**.
|
||||||
|
- La fenêtre se déplace en glissant la barre de titre.
|
||||||
|
|
||||||
|
## 8. Rechargement automatique (live-reload)
|
||||||
|
|
||||||
|
- À l'ouverture d'un fichier ou d'un dossier, l'application **surveille** en continu son
|
||||||
|
contenu sur le disque.
|
||||||
|
- Quand le **document affiché** est modifié (par un éditeur externe, un `git pull`, etc.),
|
||||||
|
il est **rechargé automatiquement** — plus besoin de revenir à l'accueil et de rouvrir le
|
||||||
|
dossier.
|
||||||
|
- Quand un fichier `.md` est **ajouté ou supprimé** dans le dossier ouvert, la **sidebar**
|
||||||
|
se met à jour automatiquement pour refléter la nouvelle arborescence (le dossier du
|
||||||
|
document courant reste déplié).
|
||||||
|
- La surveillance s'arrête au retour à l'accueil et se réinitialise à chaque nouvelle
|
||||||
|
ouverture. Détails techniques :
|
||||||
|
[Surveillance de fichiers](../2-technique/2.2-Commandes-Tauri.md#surveillance-de-fichiers-watch).
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Interface](1.3-Interface.md)
|
||||||
|
- [Thèmes et personnalisation](1.4-Themes-et-personnalisation.md)
|
||||||
|
- [Vue d'ensemble](1.1-Vue-d-ensemble.md)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Interface
|
||||||
|
|
||||||
|
L'interface de Pena se compose de deux vues principales — l'**accueil** et le **lecteur** —
|
||||||
|
surmontées d'une **barre de titre personnalisée** toujours visible.
|
||||||
|
|
||||||
|
## Barre de titre personnalisée
|
||||||
|
|
||||||
|
La décoration native de la fenêtre est désactivée ; Pena fournit sa propre barre de titre
|
||||||
|
en haut de la fenêtre (hauteur 36 px, fond sombre).
|
||||||
|
|
||||||
|
| Élément | Comportement |
|
||||||
|
|---|---|
|
||||||
|
| Titre | Affiche « Pena — Markdown Viewer » sur l'accueil, ou le titre du document ouvert dans le lecteur. |
|
||||||
|
| Zone de titre | Glisser-déposer pour déplacer la fenêtre. |
|
||||||
|
| Bouton **─** | Réduit la fenêtre. |
|
||||||
|
| Bouton **▢** | Agrandit la fenêtre, ou la restaure si elle est déjà agrandie. |
|
||||||
|
| Bouton **✕** | Ferme l'application (devient rouge au survol). |
|
||||||
|
|
||||||
|
## Vue d'accueil
|
||||||
|
|
||||||
|
C'est l'écran affiché au démarrage et quand on revient via « ← Accueil ».
|
||||||
|
|
||||||
|
Elle contient :
|
||||||
|
|
||||||
|
- Le **titre « Pena »** en grand.
|
||||||
|
- Deux boutons d'action :
|
||||||
|
- **« Ouvrir un fichier Markdown »**
|
||||||
|
- **« Ouvrir un dossier wiki »**
|
||||||
|
- Une section **« Récents »** (visible uniquement s'il y a des documents récents) listant les
|
||||||
|
derniers fichiers et dossiers ouverts, cliquables pour les rouvrir.
|
||||||
|
|
||||||
|
## Vue lecteur
|
||||||
|
|
||||||
|
Affichée dès qu'un document est ouvert. Elle est composée de deux zones :
|
||||||
|
|
||||||
|
### Barre latérale (gauche)
|
||||||
|
- Largeur fixe (264 px), fond sombre.
|
||||||
|
- En haut : le **nom du dossier** ouvert.
|
||||||
|
- Au centre : l'**arborescence des fichiers** `.md` (voir
|
||||||
|
[Navigation](1.2-Fonctionnalites.md#3-navigation-dans-la-documentation)).
|
||||||
|
- En bas (pied de barre) : deux boutons —
|
||||||
|
- **« Personnaliser CSS »** (ouvre la modale de thèmes / CSS) ;
|
||||||
|
- **« ← Accueil »** (revient à l'écran d'accueil).
|
||||||
|
|
||||||
|
### Zone de contenu (droite)
|
||||||
|
- Affiche le document Markdown rendu en HTML.
|
||||||
|
- Fond clair, typographie soignée, largeur de lecture limitée pour le confort.
|
||||||
|
- Chaque bloc de code possède un bouton **« Copier »**.
|
||||||
|
|
||||||
|
### Comportement responsive
|
||||||
|
- Sur les fenêtres larges, la barre latérale est visible en permanence à gauche.
|
||||||
|
- Sous ~1020 px de large, la barre latérale se rétracte et le contenu occupe toute la largeur.
|
||||||
|
- Sous ~540 px, les marges du contenu sont réduites.
|
||||||
|
|
||||||
|
## Modale de personnalisation CSS
|
||||||
|
|
||||||
|
Ouverte via le bouton « Personnaliser CSS » de la barre latérale. Voir la page dédiée
|
||||||
|
[Thèmes et personnalisation](1.4-Themes-et-personnalisation.md) pour le détail.
|
||||||
|
|
||||||
|
Elle se ferme via la croix, le bouton « Fermer », ou un clic en dehors de la modale.
|
||||||
|
|
||||||
|
## Palette visuelle
|
||||||
|
|
||||||
|
| Rôle | Usage |
|
||||||
|
|---|---|
|
||||||
|
| Fond sombre | Barre de titre, barre latérale |
|
||||||
|
| Fond clair | Zone de contenu |
|
||||||
|
| Accent rose/corail | Boutons, survols, liens d'action, bordures de tableaux |
|
||||||
|
| Accent indigo | Élément actif dans la barre latérale |
|
||||||
|
|
||||||
|
> L'ensemble de ces couleurs peut être surchargé par l'utilisateur via les thèmes rapides ou
|
||||||
|
> le CSS personnalisé.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Fonctionnalités](1.2-Fonctionnalites.md)
|
||||||
|
- [Thèmes et personnalisation](1.4-Themes-et-personnalisation.md)
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Thèmes et personnalisation
|
||||||
|
|
||||||
|
Pena permet de modifier entièrement l'apparence du rendu, via deux mécanismes
|
||||||
|
complémentaires : les **thèmes rapides** (prêts à l'emploi) et l'**éditeur CSS** (sur mesure).
|
||||||
|
Tout se passe dans la **modale « Personnaliser CSS »**, ouverte depuis la barre latérale.
|
||||||
|
|
||||||
|
## Thèmes rapides
|
||||||
|
|
||||||
|
Six thèmes sont fournis et gérés par le backend. Chacun est un fichier CSS intégré à
|
||||||
|
l'application.
|
||||||
|
|
||||||
|
| Identifiant | Libellé affiché |
|
||||||
|
|---|---|
|
||||||
|
| `default` | Défaut |
|
||||||
|
| `dark` | Mode sombre |
|
||||||
|
| `sepia` | Sépia |
|
||||||
|
| `large-text` | Grand texte |
|
||||||
|
| `emerald` | Accent émeraude |
|
||||||
|
| `shell-indigo` | Shell Indigo |
|
||||||
|
|
||||||
|
### Utilisation
|
||||||
|
- Les thèmes apparaissent sous forme de **badges cliquables** dans la modale.
|
||||||
|
- Un clic applique le thème **immédiatement**.
|
||||||
|
- Le badge du thème actif est mis en évidence.
|
||||||
|
- Cliquer à nouveau sur le thème actif le **désactive** (retour au rendu de base).
|
||||||
|
- Le thème choisi est mémorisé (localStorage, clé `pena_quick_theme`) et réappliqué au
|
||||||
|
prochain démarrage.
|
||||||
|
|
||||||
|
## Éditeur CSS personnalisé
|
||||||
|
|
||||||
|
Pour aller plus loin qu'un thème, la modale propose un éditeur de CSS libre, organisé en
|
||||||
|
**4 onglets** thématiques. Chaque onglet conserve son propre CSS, indépendamment des autres :
|
||||||
|
|
||||||
|
| Onglet | Vocation |
|
||||||
|
|---|---|
|
||||||
|
| Arrière-scène | Styles généraux / d'ambiance |
|
||||||
|
| Police | Typographie |
|
||||||
|
| Contenu fond | Mise en forme du contenu |
|
||||||
|
| Développement | Réglages avancés / divers |
|
||||||
|
|
||||||
|
### Fonctionnement
|
||||||
|
- On écrit du **CSS standard** dans la zone de texte de l'onglet sélectionné.
|
||||||
|
- Le bouton **« ★ Enregistrer »** sauvegarde le CSS courant et l'applique aussitôt.
|
||||||
|
- Le bouton **« Réinitialiser »** efface **tout** le CSS personnalisé **et** désactive le
|
||||||
|
thème rapide actif.
|
||||||
|
- Le bouton **« Fermer »** ferme la modale sans enregistrer les changements en cours.
|
||||||
|
- Un champ permet de nommer la configuration.
|
||||||
|
|
||||||
|
### Application combinée
|
||||||
|
Le CSS finalement appliqué est la **concaténation** du CSS des 4 onglets, suivi du CSS du
|
||||||
|
thème rapide actif. L'ordre de cascade est donc :
|
||||||
|
|
||||||
|
1. CSS de base de l'application (`style.css`) ;
|
||||||
|
2. CSS personnalisé (les 4 onglets, concaténés) ;
|
||||||
|
3. CSS du thème rapide actif (s'il y en a un).
|
||||||
|
|
||||||
|
Ainsi, un thème rapide peut compléter un CSS personnalisé, et le CSS personnalisé peut
|
||||||
|
surcharger le rendu de base.
|
||||||
|
|
||||||
|
## Persistance des préférences
|
||||||
|
|
||||||
|
Toutes les préférences sont stockées localement (localStorage) et restaurées à chaque
|
||||||
|
ouverture :
|
||||||
|
|
||||||
|
| Donnée | Clé localStorage |
|
||||||
|
|---|---|
|
||||||
|
| Thème rapide actif | `pena_quick_theme` |
|
||||||
|
| CSS onglet « Arrière-scène » | `pena_css_general` |
|
||||||
|
| CSS onglet « Police » | `pena_css_police` |
|
||||||
|
| CSS onglet « Contenu fond » | `pena_css_contenu` |
|
||||||
|
| CSS onglet « Développement » | `pena_css_avance` |
|
||||||
|
|
||||||
|
> Ces données sont propres à la machine et à l'installation : elles ne sont pas synchronisées
|
||||||
|
> et ne quittent jamais l'ordinateur.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Interface](1.3-Interface.md)
|
||||||
|
- [Fonctionnalités](1.2-Fonctionnalites.md)
|
||||||
|
- [Commandes Tauri — Thèmes](../2-technique/2.2-Commandes-Tauri.md#thèmes)
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
Pena (`Pena-tauri`) est une application [Tauri 2](https://tauri.app/) : un **backend Rust**
|
||||||
|
expose des commandes à un **frontend web** (HTML/CSS/JS vanilla, sans bundler). Le frontend
|
||||||
|
est servi en fichiers statiques depuis `src/`, le backend est compilé dans `src-tauri/`.
|
||||||
|
|
||||||
|
L'ensemble suit une **Clean Architecture** stricte, côté Rust comme côté JS.
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────── Frontend (src/, JS vanilla) ────────────────────────────┐
|
||||||
|
│ ui/ — composants d'affichage (home, sidebar, reader, css-modal) │
|
||||||
|
│ state/ — état global minimal (app-state) │
|
||||||
|
│ services/ — appels Tauri via invoke (markdown, files, watcher, themes) │
|
||||||
|
│ router.js — navigation entre vues (accueil / lecteur) │
|
||||||
|
└──────────────────────────────────────┬───────────────────────────────────────────────┘
|
||||||
|
│ window.__TAURI__.core.invoke(...)
|
||||||
|
│ window.__TAURI__.event.listen(...)
|
||||||
|
┌──────────────────────────────────────┴──── Backend (src-tauri/src/, Rust) ───────────┐
|
||||||
|
│ commands/ — points d'entrée #[tauri::command] (render, watch, theme) │
|
||||||
|
│ application/ — services métier (render_service, file_service, theme_service) │
|
||||||
|
│ infrastructure/ — implémentations concrètes (comrak, notify, fs, thèmes statiques) │
|
||||||
|
│ domain/ — entités et traits purs (MarkdownRenderer, ThemeRepository…) │
|
||||||
|
│ lib.rs — point d'assemblage (wiring + enregistrement des commandes) │
|
||||||
|
└───────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Rust — couches
|
||||||
|
|
||||||
|
Le code Rust (`src-tauri/src/`) est découpé en quatre couches, avec des règles de dépendance
|
||||||
|
strictes.
|
||||||
|
|
||||||
|
| Couche | Rôle | Peut dépendre de | Ne doit PAS dépendre de |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `domain/` | Entités et traits purs, zéro dépendance externe | rien | `application`, `infrastructure`, `commands` |
|
||||||
|
| `application/` | Services métier (orchestration des cas d'usage) | `domain` | `infrastructure`, `commands` |
|
||||||
|
| `infrastructure/` | Implémentations concrètes des traits du domaine | `domain` | `application`, `commands` |
|
||||||
|
| `commands/` | Commandes exposées à Tauri | `application`, `domain` | `infrastructure` (directement) |
|
||||||
|
|
||||||
|
### `domain/`
|
||||||
|
Définit les contrats, sans aucune dépendance technique :
|
||||||
|
- `markdown.rs` — trait `MarkdownRenderer { fn render(&self, content: &str) -> String }`
|
||||||
|
et struct `RenderOptions` (tables, strikethrough, autolink, tasklist).
|
||||||
|
- `theme.rs` — struct `Theme { id, label }` et trait
|
||||||
|
`ThemeRepository { fn list() -> Vec<Theme>; fn get_css(id) -> Option<String> }`.
|
||||||
|
|
||||||
|
### `application/`
|
||||||
|
Orchestration métier, dépend uniquement du domaine :
|
||||||
|
- `render_service.rs` — `render_string()` (rendu d'une chaîne) et `render_file()`
|
||||||
|
(lecture du fichier puis rendu).
|
||||||
|
- `file_service.rs` — `list_markdown_files()` : valide le répertoire, collecte les `.md`,
|
||||||
|
trie alphabétiquement.
|
||||||
|
- `theme_service.rs` — `list_themes()` et `get_theme_css()`, délégués au repository.
|
||||||
|
|
||||||
|
### `infrastructure/`
|
||||||
|
Implémentations concrètes des traits du domaine :
|
||||||
|
- `comrak_renderer.rs` — `ComrakRenderer` (toutes les extensions activées, pour les
|
||||||
|
fichiers) et `ComrakPreviewRenderer` (sans extensions, pour les aperçus rapides). Fournit
|
||||||
|
aussi `syntax_css_for_theme()` pour générer le CSS de coloration `syntect`. La syntaxe
|
||||||
|
TypeScript est chargée depuis `resources/syntaxes/TypeScript.sublime-syntax`.
|
||||||
|
- `file_repository.rs` — `collect_md_files()` (parcours récursif, ignore les fichiers
|
||||||
|
cachés, ne retient que les `.md`) et `read_file()`.
|
||||||
|
- `notify_watcher.rs` — boucle de débounce (80 ms) qui filtre les événements de la crate
|
||||||
|
`notify` et émet l'événement Tauri `file-changed`.
|
||||||
|
- `theme_repository.rs` — `StaticThemeRepository` : les 6 thèmes et leur CSS sont intégrés
|
||||||
|
au binaire via `include_str!()` (fichiers `resources/themes/*.css`).
|
||||||
|
|
||||||
|
### `commands/`
|
||||||
|
Points d'entrée Tauri qui instancient les implémentations concrètes et appellent les
|
||||||
|
services. Voir la référence complète : [Commandes Tauri](2.2-Commandes-Tauri.md).
|
||||||
|
|
||||||
|
### `lib.rs` — point d'assemblage
|
||||||
|
Seul endroit où les implémentations concrètes sont câblées. Il :
|
||||||
|
- enregistre les plugins `tauri-plugin-fs` et `tauri-plugin-dialog` ;
|
||||||
|
- gère l'état global `WatcherState` (un seul watcher actif à la fois) ;
|
||||||
|
- enregistre les 8 commandes via `generate_handler![]`.
|
||||||
|
|
||||||
|
## Frontend JS — couches
|
||||||
|
|
||||||
|
Le code JS (`src/`) suit le même esprit de séparation :
|
||||||
|
|
||||||
|
| Dossier | Rôle | Peut dépendre de |
|
||||||
|
|---|---|---|
|
||||||
|
| `ui/` | Composants d'affichage, sans logique métier ni appel Tauri direct | `services/`, `state/` |
|
||||||
|
| `state/` | État global de l'application (`app-state.js`) | — |
|
||||||
|
| `services/` | Appels Tauri (`invoke`) et logique de données | API Tauri |
|
||||||
|
| `router.js` | Navigation entre les vues (accueil / lecteur), orchestrateur | `services/`, `ui/`, `state/` |
|
||||||
|
|
||||||
|
**Règle importante** : les composants `ui/` ne font **jamais** d'appel Tauri directement ;
|
||||||
|
ils passent toujours par un service de `services/`.
|
||||||
|
|
||||||
|
### Services frontend
|
||||||
|
- `services/markdown.js` — `render_markdown`, `convert_file`
|
||||||
|
- `services/files.js` — `list_md_files`
|
||||||
|
- `services/watcher.js` — `start_watch`, `stop_watch`, écoute de `file-changed`
|
||||||
|
(présent mais non utilisé par l'UI)
|
||||||
|
- `services/themes.js` — `list_themes`, `get_theme_css`
|
||||||
|
|
||||||
|
## Dépendances Rust principales
|
||||||
|
|
||||||
|
| Crate | Rôle |
|
||||||
|
|---|---|
|
||||||
|
| `tauri` (+ `tauri-plugin-fs`, `tauri-plugin-dialog`) | Framework applicatif, IPC, événements, dialogues |
|
||||||
|
| `comrak` | Rendu CommonMark + extensions |
|
||||||
|
| `syntect` | Coloration syntaxique des blocs de code |
|
||||||
|
| `notify` | Surveillance du système de fichiers |
|
||||||
|
| `serde` / `serde_json` | Sérialisation des DTO et payloads |
|
||||||
|
|
||||||
|
## Ressources intégrées au binaire
|
||||||
|
|
||||||
|
| Ressource | Emplacement | Usage |
|
||||||
|
|---|---|---|
|
||||||
|
| Thèmes rapides | `src-tauri/resources/themes/*.css` | Servis par `get_theme_css` |
|
||||||
|
| Syntaxe TypeScript | `src-tauri/resources/syntaxes/TypeScript.sublime-syntax` | Coloration des blocs TS |
|
||||||
|
|
||||||
|
Elles sont incluses à la compilation (`include_str!`), donc l'application n'a pas besoin de
|
||||||
|
fichiers externes pour fonctionner.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Commandes Tauri](2.2-Commandes-Tauri.md)
|
||||||
|
- [Build et compilation](../3-installation/3.3-Build.md)
|
||||||
|
- [Vue d'ensemble fonctionnelle](../1-fonctionnel/1.1-Vue-d-ensemble.md)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Commandes Tauri
|
||||||
|
|
||||||
|
Le backend Rust expose **8 commandes** au frontend, enregistrées dans `lib.rs` via
|
||||||
|
`generate_handler![]`. Le frontend les appelle avec
|
||||||
|
`window.__TAURI__.core.invoke('<nom>', { <args> })`.
|
||||||
|
|
||||||
|
Cette page sert de référence : signature, arguments, valeur de retour et comportement de
|
||||||
|
chaque commande.
|
||||||
|
|
||||||
|
## Rendu Markdown
|
||||||
|
|
||||||
|
### `render_markdown`
|
||||||
|
```
|
||||||
|
render_markdown(content: String) -> String
|
||||||
|
```
|
||||||
|
- **Args** : `content` — Markdown brut.
|
||||||
|
- **Retour** : HTML.
|
||||||
|
- **Comportement** : rendu via `ComrakPreviewRenderer` (sans extensions, aperçu rapide).
|
||||||
|
- **Frontend** : `services/markdown.js`.
|
||||||
|
|
||||||
|
### `convert_file`
|
||||||
|
```
|
||||||
|
convert_file(path: String) -> Result<String, String>
|
||||||
|
```
|
||||||
|
- **Args** : `path` — chemin absolu d'un fichier `.md`.
|
||||||
|
- **Retour** : `Ok(html)` ou `Err(message)`.
|
||||||
|
- **Comportement** : lit le fichier puis le rend via `ComrakRenderer` (toutes les extensions
|
||||||
|
activées : tables, strikethrough, autolink, tasklist + coloration syntaxique).
|
||||||
|
- **Frontend** : `services/markdown.js`. C'est la commande utilisée pour afficher un document
|
||||||
|
dans le lecteur.
|
||||||
|
|
||||||
|
### `get_syntax_highlight_css`
|
||||||
|
```
|
||||||
|
get_syntax_highlight_css() -> String
|
||||||
|
```
|
||||||
|
- **Args** : aucun.
|
||||||
|
- **Retour** : feuille CSS de coloration syntaxique.
|
||||||
|
- **Comportement** : génère le CSS du thème `syntect` `base16-ocean.dark`.
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
|
||||||
|
### `list_md_files`
|
||||||
|
```
|
||||||
|
list_md_files(dir: String) -> Result<Vec<String>, String>
|
||||||
|
```
|
||||||
|
- **Args** : `dir` — chemin d'un répertoire.
|
||||||
|
- **Retour** : `Ok([chemins…])` (triés alphabétiquement) ou `Err(message)`.
|
||||||
|
- **Comportement** : parcourt le répertoire **récursivement**, ignore les fichiers/dossiers
|
||||||
|
cachés (commençant par `.`), ne retient que les `.md`.
|
||||||
|
- **Frontend** : `services/files.js`.
|
||||||
|
|
||||||
|
## Surveillance de fichiers (watch)
|
||||||
|
|
||||||
|
> Ces commandes sont implémentées et fonctionnelles côté backend, mais **l'interface ne les
|
||||||
|
> appelle pas encore**. Le rechargement automatique n'est donc pas actif. Voir
|
||||||
|
> [Fonctionnalités](../1-fonctionnel/1.2-Fonctionnalites.md#fonctionnalité-présente-mais-non-branchée).
|
||||||
|
|
||||||
|
### `start_watch`
|
||||||
|
```
|
||||||
|
start_watch(path: String) -> Result<(), String>
|
||||||
|
```
|
||||||
|
- **Args** : `path` — fichier ou répertoire à surveiller.
|
||||||
|
- **Comportement** :
|
||||||
|
- arrête le watcher précédent (un seul actif à la fois, stocké dans `WatcherState`) ;
|
||||||
|
- surveille en mode récursif si `path` est un répertoire, non récursif si c'est un
|
||||||
|
fichier ;
|
||||||
|
- lance une boucle de **débounce de 80 ms** qui ne retient que les fichiers `.md` non
|
||||||
|
cachés, et émet l'événement Tauri **`file-changed`** avec un payload `{ path }`.
|
||||||
|
- **Frontend** : `services/watcher.js` (`startWatch`).
|
||||||
|
|
||||||
|
### `stop_watch`
|
||||||
|
```
|
||||||
|
stop_watch() -> ()
|
||||||
|
```
|
||||||
|
- **Comportement** : arrête le watcher actif (le supprime de `WatcherState`, ce qui termine
|
||||||
|
le thread de surveillance).
|
||||||
|
- **Frontend** : `services/watcher.js` (`stopWatch`).
|
||||||
|
|
||||||
|
### Événement `file-changed`
|
||||||
|
- Émis par le backend pendant la surveillance.
|
||||||
|
- Payload : `{ path: String }` — chemin du fichier modifié.
|
||||||
|
- Écoutable côté frontend via
|
||||||
|
`window.__TAURI__.event.listen('file-changed', callback)` (`onFileChanged`).
|
||||||
|
- **Consommation** : `router.js` enregistre le listener au démarrage (`initWatcher`) et,
|
||||||
|
à chaque événement, recharge le document affiché s'il correspond au chemin modifié et
|
||||||
|
rafraîchit la sidebar si l'arborescence `.md` du dossier surveillé a changé. La
|
||||||
|
surveillance est démarrée dans `openPath` (via `startWatch`) et arrêtée dans `showHome`
|
||||||
|
(via `stopWatch`).
|
||||||
|
|
||||||
|
## Thèmes
|
||||||
|
|
||||||
|
### `list_themes`
|
||||||
|
```
|
||||||
|
list_themes() -> Vec<ThemeDto> // ThemeDto { id: String, label: String }
|
||||||
|
```
|
||||||
|
- **Args** : aucun.
|
||||||
|
- **Retour** : les 6 thèmes (`default`, `dark`, `sepia`, `large-text`, `emerald`,
|
||||||
|
`shell-indigo`) avec leur libellé.
|
||||||
|
- **Frontend** : `services/themes.js`.
|
||||||
|
|
||||||
|
### `get_theme_css`
|
||||||
|
```
|
||||||
|
get_theme_css(id: String) -> Option<String>
|
||||||
|
```
|
||||||
|
- **Args** : `id` — identifiant du thème.
|
||||||
|
- **Retour** : le CSS du thème (`Some`) ou `None` si l'identifiant est inconnu.
|
||||||
|
- **Comportement** : le CSS est intégré au binaire (`include_str!` sur
|
||||||
|
`resources/themes/<id>.css`).
|
||||||
|
- **Frontend** : `services/themes.js`.
|
||||||
|
|
||||||
|
## Récapitulatif
|
||||||
|
|
||||||
|
| Commande | Signature | Retour |
|
||||||
|
|---|---|---|
|
||||||
|
| `render_markdown` | `(content: String)` | `String` |
|
||||||
|
| `convert_file` | `(path: String)` | `Result<String, String>` |
|
||||||
|
| `get_syntax_highlight_css` | `()` | `String` |
|
||||||
|
| `list_md_files` | `(dir: String)` | `Result<Vec<String>, String>` |
|
||||||
|
| `start_watch` | `(path: String)` | `Result<(), String>` |
|
||||||
|
| `stop_watch` | `()` | `()` |
|
||||||
|
| `list_themes` | `()` | `Vec<ThemeDto>` |
|
||||||
|
| `get_theme_css` | `(id: String)` | `Option<String>` |
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Architecture](2.1-Architecture.md)
|
||||||
|
- [Thèmes et personnalisation](../1-fonctionnel/1.4-Themes-et-personnalisation.md)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Installation (Linux générique / Fedora)
|
||||||
|
|
||||||
|
Cette page décrit l'installation de **Pena-tauri** sur une distribution Linux classique
|
||||||
|
(mutable), où l'on peut installer des paquets système avec le gestionnaire de la distribution.
|
||||||
|
|
||||||
|
- Pour un système **immuable** (Bazzite, Fedora Silverblue/Kinoite, openSUSE MicroOS…), voir
|
||||||
|
[Installation sur Bazzite OS](3.2-Installation-Bazzite.md).
|
||||||
|
- Pour le détail des commandes de compilation, voir [Build](3.3-Build.md).
|
||||||
|
|
||||||
|
## 1. Installer les prérequis
|
||||||
|
|
||||||
|
### Rust et Tauri CLI
|
||||||
|
```bash
|
||||||
|
# Rust (toolchain stable)
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
|
||||||
|
# Tauri CLI v2
|
||||||
|
cargo install tauri-cli --version "^2"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dépendances système
|
||||||
|
|
||||||
|
**Fedora / RHEL :**
|
||||||
|
```bash
|
||||||
|
sudo dnf install webkit2gtk4.1-devel \
|
||||||
|
openssl-devel curl wget file libappindicator-gtk3-devel librsvg2-devel
|
||||||
|
sudo dnf group install "C Development Tools and Libraries"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debian / Ubuntu :**
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \
|
||||||
|
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Récupérer les sources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <url-du-depot> Pena
|
||||||
|
cd Pena/Pena-tauri
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Compiler et installer
|
||||||
|
|
||||||
|
### Lancer directement (développement)
|
||||||
|
```bash
|
||||||
|
cargo tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Construire une release
|
||||||
|
```bash
|
||||||
|
cargo tauri build
|
||||||
|
```
|
||||||
|
|
||||||
|
L'exécutable est produit dans `src-tauri/target/release/`. Pour l'installer à l'échelle du
|
||||||
|
système, copiez-le dans un dossier du `PATH` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -Dm755 src-tauri/target/release/pena-taury /usr/local/bin/pena
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous pouvez ensuite lancer l'application avec `pena`.
|
||||||
|
|
||||||
|
> **Nom du binaire** : le binaire s'appelle `pena-taury` (nom du paquet Cargo). Renommez-le à
|
||||||
|
> votre convenance lors de l'installation, comme ci-dessus (`pena`).
|
||||||
|
|
||||||
|
## 4. (Optionnel) Raccourci d'application
|
||||||
|
|
||||||
|
Pour faire apparaître Pena dans le menu des applications, créez un fichier `.desktop` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > ~/.local/share/applications/pena.desktop <<'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Pena
|
||||||
|
Comment=Lecteur Markdown
|
||||||
|
Exec=/usr/local/bin/pena
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;Office;
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Build et compilation](3.3-Build.md)
|
||||||
|
- [Installation sur Bazzite OS](3.2-Installation-Bazzite.md)
|
||||||
|
- [Vue d'ensemble](../1-fonctionnel/1.1-Vue-d-ensemble.md)
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
# Installation sur Bazzite OS
|
||||||
|
|
||||||
|
[Bazzite](https://bazzite.gg/) est une distribution **immuable** basée sur Fedora Atomic
|
||||||
|
(rpm-ostree). Le système de fichiers racine est en lecture seule : on **n'installe pas** de
|
||||||
|
paquets de développement (`webkit2gtk-devel`, compilateurs…) directement sur l'hôte comme on
|
||||||
|
le ferait sur une Fedora classique.
|
||||||
|
|
||||||
|
Cette contrainte vaut aussi pour les autres systèmes atomiques (Fedora Silverblue, Kinoite,
|
||||||
|
Universal Blue, openSUSE MicroOS…).
|
||||||
|
|
||||||
|
La méthode recommandée est le **Flatpak** : on construit **une seule fois** un fichier
|
||||||
|
`.flatpak` (le « bundle »), on l'héberge (par ex. sur un dépôt Gitea), puis on l'installe sur
|
||||||
|
n'importe quelle machine **en une commande**, sans rien compiler ni installer de dépendance
|
||||||
|
sur l'hôte.
|
||||||
|
|
||||||
|
| Vous voulez… | Allez à… |
|
||||||
|
|---|---|
|
||||||
|
| **Installer Pena** depuis un `.flatpak` déjà construit (récupéré sur Gitea) | [1. Méthode simple](#1-méthode-simple--installer-le-paquet-pré-construit) |
|
||||||
|
| **Produire** le fichier `.flatpak` (une fois, sur une machine de build) | [2. Compiler](#2-compiler-le-binaire-une-fois) puis [3. Construire le bundle](#3-construire-le-paquet-flatpak-une-fois) |
|
||||||
|
| **Développer / itérer** sur le code sans empaqueter | [4. Alternative développeur (distrobox)](#4-alternative-développeur--lancer-via-distrobox) |
|
||||||
|
|
||||||
|
> Bazzite fournit **`flatpak`** (avec le dépôt Flathub), **`distrobox`** et **`podman`**
|
||||||
|
> préinstallés. Aucune surcouche rpm-ostree (`rpm-ostree install`) n'est nécessaire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Méthode simple — installer le paquet pré-construit
|
||||||
|
|
||||||
|
C'est le scénario du quotidien : le fichier `pena.flatpak` a déjà été construit (voir
|
||||||
|
sections 2 et 3) et déposé sur votre Gitea. Sur la machine cible, il suffit de le récupérer et
|
||||||
|
de l'installer.
|
||||||
|
|
||||||
|
### 1.1 — Installer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Récupérer le bundle depuis Gitea (adaptez l'URL à votre dépôt)
|
||||||
|
curl -L -o pena.flatpak \
|
||||||
|
https://git.goutailler-olivier.com/<utilisateur>/<depot>/raw/branch/main/pena.flatpak
|
||||||
|
|
||||||
|
# Installer pour l'utilisateur courant (aucun droit root nécessaire)
|
||||||
|
flatpak install --user pena.flatpak
|
||||||
|
```
|
||||||
|
|
||||||
|
> Le runtime `org.gnome.Platform` (qui fournit WebKitGTK) est téléchargé automatiquement
|
||||||
|
> depuis Flathub lors de l'installation s'il n'est pas déjà présent. C'est la seule dépendance,
|
||||||
|
> et elle est gérée par Flatpak — rien à compiler ni à installer sur l'hôte.
|
||||||
|
|
||||||
|
### 1.2 — Lancer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flatpak run com.pena.app
|
||||||
|
```
|
||||||
|
|
||||||
|
Pena apparaît aussi dans le menu des applications de Bazzite.
|
||||||
|
|
||||||
|
### 1.3 — Mettre à jour / désinstaller
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mettre à jour : récupérer le nouveau bundle puis réinstaller par-dessus
|
||||||
|
flatpak install --user --reinstall pena.flatpak
|
||||||
|
|
||||||
|
# Désinstaller
|
||||||
|
flatpak uninstall com.pena.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 — (Optionnel) Installation en une ligne
|
||||||
|
|
||||||
|
Vous pouvez déposer à côté du bundle, sur Gitea, un script `install.sh` qui automatise le
|
||||||
|
téléchargement et l'installation :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
URL="https://git.goutailler-olivier.com/<utilisateur>/<depot>/raw/branch/main/pena.flatpak"
|
||||||
|
TMP="$(mktemp --suffix=.flatpak)"
|
||||||
|
curl -L -o "$TMP" "$URL"
|
||||||
|
flatpak install --user -y "$TMP"
|
||||||
|
rm -f "$TMP"
|
||||||
|
echo "Pena installé. Lancez-le avec : flatpak run com.pena.app"
|
||||||
|
```
|
||||||
|
|
||||||
|
L'installation se résume alors à :
|
||||||
|
```bash
|
||||||
|
curl -L https://git.goutailler-olivier.com/<utilisateur>/<depot>/raw/branch/main/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Compiler le binaire (une fois)
|
||||||
|
|
||||||
|
Cette étape et la suivante se font **sur une machine de build** (la vôtre, dans un container).
|
||||||
|
Le résultat est un fichier `pena.flatpak` que vous n'aurez plus qu'à héberger.
|
||||||
|
|
||||||
|
Pena se compile dans un container Ubuntu via **distrobox**, pour ne rien installer sur l'hôte
|
||||||
|
immuable. WebKitGTK et le compilateur restent dans le container.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Créer et entrer dans un container Ubuntu
|
||||||
|
distrobox create --name pena-build --image ubuntu:24.04
|
||||||
|
distrobox enter pena-build
|
||||||
|
|
||||||
|
# 2. (dans le container) installer les dépendances de build
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y \
|
||||||
|
libwebkit2gtk-4.1-dev build-essential curl wget file \
|
||||||
|
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev git
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
cargo install tauri-cli --version "^2"
|
||||||
|
|
||||||
|
# 3. (dans le container) compiler
|
||||||
|
cd ~/Pena/Pena-tauri # adaptez le chemin vers les sources
|
||||||
|
cargo tauri build
|
||||||
|
```
|
||||||
|
|
||||||
|
Le binaire est produit dans `src-tauri/target/release/pena-taury`. Vous pouvez ensuite
|
||||||
|
quitter le container (`exit`) : la suite (section 3) se fait sur l'hôte, où `flatpak` est
|
||||||
|
disponible. Comme `$HOME` est partagé entre l'hôte et le container, le binaire compilé est
|
||||||
|
accessible des deux côtés.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Construire le paquet `.flatpak` (une fois)
|
||||||
|
|
||||||
|
À partir du binaire compilé, on produit le fichier unique `pena.flatpak`.
|
||||||
|
|
||||||
|
### 3.1 — Outillage Flatpak (sur l'hôte)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flatpak install -y flathub org.gnome.Platform//47 org.gnome.Sdk//47 org.flatpak.Builder
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 — Fichiers d'empaquetage
|
||||||
|
|
||||||
|
Depuis la racine `Pena-tauri/`, créez deux fichiers.
|
||||||
|
|
||||||
|
**`com.pena.app.desktop`** :
|
||||||
|
```ini
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Pena
|
||||||
|
Comment=Lecteur Markdown
|
||||||
|
Exec=pena
|
||||||
|
Icon=com.pena.app
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;Office;
|
||||||
|
```
|
||||||
|
|
||||||
|
**`com.pena.app.yml`** (manifeste Flatpak) :
|
||||||
|
```yaml
|
||||||
|
id: com.pena.app
|
||||||
|
runtime: org.gnome.Platform
|
||||||
|
runtime-version: '47'
|
||||||
|
sdk: org.gnome.Sdk
|
||||||
|
command: pena
|
||||||
|
|
||||||
|
finish-args:
|
||||||
|
- --share=ipc
|
||||||
|
- --socket=wayland
|
||||||
|
- --socket=fallback-x11
|
||||||
|
- --device=dri
|
||||||
|
# Accès en lecture aux fichiers Markdown de l'utilisateur
|
||||||
|
- --filesystem=home:ro
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: pena
|
||||||
|
buildsystem: simple
|
||||||
|
build-commands:
|
||||||
|
- install -Dm755 pena-taury /app/bin/pena
|
||||||
|
- install -Dm644 com.pena.app.desktop /app/share/applications/com.pena.app.desktop
|
||||||
|
- install -Dm644 icon.png /app/share/icons/hicolor/512x512/apps/com.pena.app.png
|
||||||
|
sources:
|
||||||
|
- type: file
|
||||||
|
path: src-tauri/target/release/pena-taury
|
||||||
|
- type: file
|
||||||
|
path: com.pena.app.desktop
|
||||||
|
- type: file
|
||||||
|
path: src-tauri/icons/icon.png
|
||||||
|
```
|
||||||
|
|
||||||
|
> L'identifiant `com.pena.app` correspond au champ `identifier` de
|
||||||
|
> `src-tauri/tauri.conf.json`. Le runtime `org.gnome.Platform` embarque WebKitGTK : l'app
|
||||||
|
> fonctionnera sans aucune dépendance installée sur la machine cible.
|
||||||
|
|
||||||
|
### 3.3 — Construire le bundle
|
||||||
|
|
||||||
|
Depuis `Pena-tauri/` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Construire l'app dans un dépôt OSTree local (dossier "repo")
|
||||||
|
flatpak run org.flatpak.Builder --force-clean --repo=repo \
|
||||||
|
--install-deps-from=flathub build-dir com.pena.app.yml
|
||||||
|
|
||||||
|
# 2. Exporter en UN SEUL fichier .flatpak
|
||||||
|
flatpak build-bundle repo pena.flatpak com.pena.app \
|
||||||
|
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous obtenez **`pena.flatpak`** : c'est ce fichier unique, autoportant, que vous installez
|
||||||
|
partout (section 1). L'option `--runtime-repo` y inscrit la référence vers Flathub, pour que
|
||||||
|
le runtime soit récupéré automatiquement à l'installation.
|
||||||
|
|
||||||
|
### 3.4 — Héberger sur Gitea
|
||||||
|
|
||||||
|
Déposez `pena.flatpak` sur votre dépôt Gitea, soit :
|
||||||
|
- en l'ajoutant au dépôt (`git add pena.flatpak`) — simple, mais alourdit l'historique ;
|
||||||
|
- **de préférence**, en l'attachant à une **release** Gitea (onglet *Releases* → *New Release*
|
||||||
|
→ joindre le binaire). L'URL de téléchargement direct est alors stable et n'encombre pas le
|
||||||
|
dépôt.
|
||||||
|
|
||||||
|
Adaptez ensuite l'URL utilisée à la [section 1.1](#11--installer).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Alternative développeur — lancer via distrobox
|
||||||
|
|
||||||
|
Si vous **développez** sur Pena et n'avez pas besoin d'un paquet installable, vous pouvez
|
||||||
|
exécuter l'application directement depuis le container, sans passer par Flatpak.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lancement direct (la fenêtre s'affiche sur le bureau de l'hôte)
|
||||||
|
distrobox enter pena-build -- bash -lc 'cd ~/Pena/Pena-tauri && cargo tauri dev'
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour exposer le binaire compilé à l'hôte comme une commande :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Depuis le container, après "cargo tauri build"
|
||||||
|
distrobox-export --bin ~/Pena/Pena-tauri/src-tauri/target/release/pena-taury \
|
||||||
|
--export-path ~/.local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Le binaire devient lançable depuis l'hôte (`~/.local/bin` doit être dans le `PATH`). Cette
|
||||||
|
voie est pratique pour itérer, mais le Flatpak (sections 1–3) reste la méthode recommandée
|
||||||
|
pour une **installation** propre et reproductible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note — AppImage
|
||||||
|
|
||||||
|
Une autre forme de « fichier unique à exécuter » est l'**AppImage** : un binaire autoportant
|
||||||
|
qui se lance sans installation (`chmod +x Pena.AppImage && ./Pena.AppImage`). Tauri sait en
|
||||||
|
produire, mais cela nécessite d'activer le bundler (`bundle.active: true` dans
|
||||||
|
`tauri.conf.json`, cible `appimage`) et l'outillage associé. Le **bundle Flatpak** décrit
|
||||||
|
ci-dessus est privilégié ici car il s'intègre au menu, se met à jour proprement et gère
|
||||||
|
automatiquement ses dépendances via Flathub.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Release et distribution](3.4-Release-et-distribution.md) — pourquoi le Flatpak, portabilité et trade-offs des formats
|
||||||
|
- [Build et compilation](3.3-Build.md)
|
||||||
|
- [Installation (Linux générique / Fedora)](3.1-Installation.md)
|
||||||
|
- [Architecture](../2-technique/2.1-Architecture.md)
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Build et compilation
|
||||||
|
|
||||||
|
Cette page décrit comment compiler **Pena-tauri** depuis les sources, en développement comme
|
||||||
|
en production. Pour une installation sur un système immuable (Bazzite, Silverblue, etc.),
|
||||||
|
voir plutôt [Installation sur Bazzite OS](3.2-Installation-Bazzite.md).
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
| Outil | Détail |
|
||||||
|
|---|---|
|
||||||
|
| **Rust** | Toolchain stable, via [rustup](https://rustup.rs/) (édition 2021). |
|
||||||
|
| **Tauri CLI v2** | `cargo install tauri-cli --version "^2"` (fournit `cargo tauri`). |
|
||||||
|
| **Dépendances système** | WebKitGTK et libs associées (voir ci-dessous selon la distribution). |
|
||||||
|
|
||||||
|
Le frontend n'a **aucune dépendance Node** : c'est du JS vanilla servi statiquement depuis
|
||||||
|
`src/`. Il n'y a donc pas de `npm install` à faire pour `Pena-tauri`.
|
||||||
|
|
||||||
|
### Dépendances système Linux
|
||||||
|
|
||||||
|
**Fedora / RHEL :**
|
||||||
|
```bash
|
||||||
|
sudo dnf install webkit2gtk4.1-devel \
|
||||||
|
openssl-devel curl wget file libappindicator-gtk3-devel librsvg2-devel
|
||||||
|
sudo dnf group install "C Development Tools and Libraries"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debian / Ubuntu :**
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \
|
||||||
|
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
> Sur une distribution immuable (Bazzite, Fedora Silverblue, Kinoite…), n'installez pas ces
|
||||||
|
> paquets sur l'hôte : utilisez un container ou un Flatpak. Voir
|
||||||
|
> [Installation sur Bazzite OS](3.2-Installation-Bazzite.md).
|
||||||
|
|
||||||
|
## Lancer en développement
|
||||||
|
|
||||||
|
Depuis `Pena-tauri/` :
|
||||||
|
```bash
|
||||||
|
cargo tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- Le frontend est servi directement depuis `src/` (fichiers statiques, sans bundler).
|
||||||
|
- La compilation Rust est lancée automatiquement et l'application s'ouvre.
|
||||||
|
- Toute modification du Rust déclenche une recompilation.
|
||||||
|
|
||||||
|
## Construire une release
|
||||||
|
|
||||||
|
Depuis `Pena-tauri/` :
|
||||||
|
```bash
|
||||||
|
cargo tauri build
|
||||||
|
```
|
||||||
|
|
||||||
|
L'exécutable est produit dans :
|
||||||
|
```
|
||||||
|
src-tauri/target/release/
|
||||||
|
```
|
||||||
|
|
||||||
|
### À propos du bundling
|
||||||
|
|
||||||
|
Dans `src-tauri/tauri.conf.json`, le bundling est **désactivé** :
|
||||||
|
```json
|
||||||
|
"bundle": { "active": false, "targets": "all", "icon": [] }
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo tauri build` produit donc l'**exécutable natif** (dans `target/release/`), mais ne
|
||||||
|
génère pas de paquets `.deb`, `.rpm` ou `.AppImage`. Pour activer la génération de ces
|
||||||
|
paquets, passer `bundle.active` à `true` et renseigner les icônes.
|
||||||
|
|
||||||
|
## Compiler le backend seul (sans Tauri CLI)
|
||||||
|
|
||||||
|
Pour de la compilation / des tests bas niveau, depuis `src-tauri/` :
|
||||||
|
```bash
|
||||||
|
cargo build # compilation debug
|
||||||
|
cargo build --release # compilation optimisée
|
||||||
|
cargo test # tests unitaires
|
||||||
|
cargo clippy -- -D warnings # lint (zéro warning toléré)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Qualité et couverture
|
||||||
|
|
||||||
|
Le dépôt `Pena-tauri` impose un hook `pre-commit` qui bloque le commit si :
|
||||||
|
- **Clippy** remonte le moindre warning (`-D warnings`) ;
|
||||||
|
- la **couverture de lignes** est inférieure à **60 %** (via `cargo-llvm-cov`).
|
||||||
|
|
||||||
|
Installer l'outil de couverture :
|
||||||
|
```bash
|
||||||
|
cargo install cargo-llvm-cov
|
||||||
|
cargo llvm-cov --summary-only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Récapitulatif des commandes
|
||||||
|
|
||||||
|
| But | Commande (depuis `Pena-tauri/`) |
|
||||||
|
|---|---|
|
||||||
|
| Développement | `cargo tauri dev` |
|
||||||
|
| Release (exécutable) | `cargo tauri build` |
|
||||||
|
| Compiler le backend | `cargo build --release` (depuis `src-tauri/`) |
|
||||||
|
| Tests | `cargo test` (depuis `src-tauri/`) |
|
||||||
|
| Lint | `cargo clippy -- -D warnings` |
|
||||||
|
| Couverture | `cargo llvm-cov --summary-only` |
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Release et distribution](3.4-Release-et-distribution.md) — portabilité du binaire et création d'un paquet Flatpak
|
||||||
|
- [Installation (Linux générique)](3.1-Installation.md)
|
||||||
|
- [Installation sur Bazzite OS](3.2-Installation-Bazzite.md)
|
||||||
|
- [Architecture](../2-technique/2.1-Architecture.md)
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Release et distribution
|
||||||
|
|
||||||
|
Cette page explique **comment fonctionne une release de Pena** : ce que produit réellement la
|
||||||
|
compilation, **si un binaire compilé sur une distribution Linux est utilisable sur n'importe
|
||||||
|
quelle autre**, et les différentes façons de **distribuer** l'application — avec un focus sur le
|
||||||
|
**paquet Flatpak** et ses avantages/inconvénients.
|
||||||
|
|
||||||
|
- Pour les commandes de compilation pures, voir [Build et compilation](3.3-Build.md).
|
||||||
|
- Pour la procédure pas-à-pas d'installation Flatpak sur système immuable, voir
|
||||||
|
[Installation sur Bazzite OS](3.2-Installation-Bazzite.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Que produit une release ?
|
||||||
|
|
||||||
|
`cargo tauri build` (depuis `Pena-tauri/`) produit un **exécutable natif** :
|
||||||
|
|
||||||
|
```
|
||||||
|
src-tauri/target/release/pena-taury
|
||||||
|
```
|
||||||
|
|
||||||
|
C'est un binaire ELF compilé pour l'architecture de la machine de build (typiquement
|
||||||
|
`x86_64-linux-gnu`). Le frontend (HTML/CSS/JS de `src/`) y est **embarqué** dans le binaire :
|
||||||
|
il n'y a pas de fichiers web à distribuer à côté.
|
||||||
|
|
||||||
|
> Dans `tauri.conf.json`, `bundle.active` vaut `false` : `cargo tauri build` ne génère donc
|
||||||
|
> **pas** de `.deb`/`.rpm`/`.AppImage` automatiquement. Il produit l'exécutable seul. La
|
||||||
|
> génération de paquets est traitée plus bas (section 4).
|
||||||
|
|
||||||
|
### Ce que le binaire contient — et ce qu'il ne contient pas
|
||||||
|
|
||||||
|
| Embarqué dans le binaire | **Pas** embarqué (fourni par le système) |
|
||||||
|
|---|---|
|
||||||
|
| Code Rust de Pena (commandes Tauri) | **glibc** (bibliothèque C) |
|
||||||
|
| Frontend (HTML/CSS/JS) | **WebKitGTK** (le moteur de rendu de la fenêtre) |
|
||||||
|
| `comrak` + `syntect` (rendu Markdown) | **GTK 3/4**, GLib, Cairo, Pango… |
|
||||||
|
| Plugins Tauri liés statiquement | Pilotes graphiques, serveur d'affichage (X11/Wayland) |
|
||||||
|
|
||||||
|
C'est ce tableau qui répond à la question de la portabilité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Un binaire compilé sur Linux marche-t-il sur **n'importe quelle** distribution ?
|
||||||
|
|
||||||
|
**Réponse courte : non, pas de façon fiable.** Un binaire « brut » (le `pena-taury` produit
|
||||||
|
ci-dessus) n'est **pas universel**. Il dépend de bibliothèques présentes sur le système cible,
|
||||||
|
et il ne tournera ailleurs que si ces bibliothèques sont **présentes et compatibles**.
|
||||||
|
|
||||||
|
Trois raisons concrètes :
|
||||||
|
|
||||||
|
### 2.1 — Le lien dynamique avec WebKitGTK
|
||||||
|
|
||||||
|
C'est la contrainte la plus dure pour une app Tauri. Le binaire est lié à
|
||||||
|
**`libwebkit2gtk-4.1`**. Sur la machine cible, il faut :
|
||||||
|
|
||||||
|
- que WebKitGTK soit **installé** ;
|
||||||
|
- que ce soit la **bonne version d'ABI** : `webkit2gtk-4.0` et `webkit2gtk-4.1` ont des
|
||||||
|
*sonames* différents et **ne sont pas interchangeables**. Un binaire lié à `4.1` ne démarrera
|
||||||
|
pas sur une machine qui n'a que `4.0`, et inversement.
|
||||||
|
|
||||||
|
Sur une distribution récente, WebKitGTK 4.1 est généralement disponible ; sur une
|
||||||
|
distribution plus ancienne ou minimaliste, il peut manquer.
|
||||||
|
|
||||||
|
### 2.2 — La version de la glibc
|
||||||
|
|
||||||
|
Les binaires liés à la glibc sont **compatibles vers l'avant, pas vers l'arrière** :
|
||||||
|
|
||||||
|
- un binaire compilé sur une **vieille** glibc tourne sur une machine ayant une glibc **plus
|
||||||
|
récente** ✅ ;
|
||||||
|
- un binaire compilé sur une glibc **récente** échoue sur une machine ayant une glibc **plus
|
||||||
|
ancienne** ❌, avec une erreur du type `version 'GLIBC_2.38' not found`.
|
||||||
|
|
||||||
|
**Conséquence pratique :** pour maximiser la portabilité d'un binaire brut, compilez-le sur la
|
||||||
|
distribution la **plus ancienne** que vous comptez supporter (par ex. dans un container
|
||||||
|
Ubuntu 22.04), pas sur la plus récente.
|
||||||
|
|
||||||
|
### 2.3 — Les autres bibliothèques système
|
||||||
|
|
||||||
|
GTK, GLib, Cairo, Pango, librsvg, libappindicator… doivent aussi être présentes. Sur un poste
|
||||||
|
de bureau classique elles le sont presque toujours (ce sont des dépendances de l'environnement
|
||||||
|
de bureau), mais sur un serveur, un système minimal ou immuable, ce n'est pas garanti.
|
||||||
|
|
||||||
|
### Récapitulatif portabilité
|
||||||
|
|
||||||
|
| Cible | Le binaire brut marche-t-il ? |
|
||||||
|
|---|---|
|
||||||
|
| Même distribution / version que la machine de build | ✅ Oui |
|
||||||
|
| Distribution différente mais récente, avec `webkit2gtk-4.1` et glibc ≥ celle du build | ✅ Généralement |
|
||||||
|
| Distribution avec une glibc **plus ancienne** que le build | ❌ Non (`GLIBC_x.y not found`) |
|
||||||
|
| Distribution qui n'a que `webkit2gtk-4.0` | ❌ Non (soname incompatible) |
|
||||||
|
| Système immuable (Bazzite, Silverblue…) | ❌ Pas directement — voir Flatpak |
|
||||||
|
| Architecture différente (ARM vs x86_64) | ❌ Non — il faut recompiler pour l'archi |
|
||||||
|
|
||||||
|
**Conclusion :** « compilé sur Linux » ne veut pas dire « marche sur tout Linux ». Pour une
|
||||||
|
distribution **réellement universelle**, il faut un format qui **embarque ses dépendances** :
|
||||||
|
c'est le rôle du **Flatpak** (recommandé ici) ou de l'**AppImage**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Les formats de distribution possibles
|
||||||
|
|
||||||
|
| Format | Dépendances | Portabilité | Intégration bureau | Mise à jour |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Binaire brut** | Fournies par l'hôte | Faible (voir §2) | Manuelle (`.desktop`) | Manuelle |
|
||||||
|
| **`.deb` / `.rpm`** | Déclarées, résolues par le gestionnaire de paquets | Bonne **sur la famille visée** (Debian *ou* Fedora) | Automatique | Via le gestionnaire |
|
||||||
|
| **AppImage** | **Embarquées** dans un fichier exécutable | Bonne (un seul fichier portable) | Partielle | Manuelle (re-télécharger) |
|
||||||
|
| **Flatpak** | **Embarquées** via un *runtime* partagé (Flathub) | **Excellente** (toute distro avec `flatpak`) | Complète (menu, icône) | `flatpak update` |
|
||||||
|
|
||||||
|
Pour Pena, le format **recommandé est le Flatpak** : il résout d'un coup les trois problèmes du
|
||||||
|
§2 (WebKitGTK, glibc, libs système) en fournissant un **runtime** complet et identique partout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Créer un paquet Flatpak
|
||||||
|
|
||||||
|
Le Flatpak rend l'application **vraiment portable** : le moteur WebKitGTK et toutes les
|
||||||
|
bibliothèques système viennent du **runtime `org.gnome.Platform`** (téléchargé depuis Flathub),
|
||||||
|
pas de l'hôte. Le même `pena.flatpak` s'installe alors sur n'importe quelle distribution dotée
|
||||||
|
de `flatpak`, immuable ou non.
|
||||||
|
|
||||||
|
> La procédure complète (installation côté machine cible, hébergement sur Gitea, alternative
|
||||||
|
> développeur) est détaillée dans [Installation sur Bazzite OS](3.2-Installation-Bazzite.md).
|
||||||
|
> On résume ici les étapes de **production** du paquet.
|
||||||
|
|
||||||
|
### 4.1 — Principe en trois temps
|
||||||
|
|
||||||
|
```
|
||||||
|
[ cargo tauri build ] → binaire natif pena-taury
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ manifeste Flatpak ] → build dans un dépôt OSTree local "repo/"
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ flatpak build-bundle ] → fichier unique pena.flatpak
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 — Outillage (une fois)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flatpak install -y flathub org.gnome.Platform//47 org.gnome.Sdk//47 org.flatpak.Builder
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 — Le manifeste
|
||||||
|
|
||||||
|
Le manifeste décrit l'identifiant de l'app, le runtime qui fournit les dépendances, et comment
|
||||||
|
installer le binaire dans le sandbox. Depuis `Pena-tauri/`, créer **`com.pena.app.yml`** :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: com.pena.app # = identifier de tauri.conf.json
|
||||||
|
runtime: org.gnome.Platform # fournit WebKitGTK + GTK + glibc du runtime
|
||||||
|
runtime-version: '47'
|
||||||
|
sdk: org.gnome.Sdk
|
||||||
|
command: pena
|
||||||
|
|
||||||
|
finish-args:
|
||||||
|
- --share=ipc
|
||||||
|
- --socket=wayland
|
||||||
|
- --socket=fallback-x11
|
||||||
|
- --device=dri
|
||||||
|
- --filesystem=home:ro # lecture des fichiers Markdown de l'utilisateur
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: pena
|
||||||
|
buildsystem: simple
|
||||||
|
build-commands:
|
||||||
|
- install -Dm755 pena-taury /app/bin/pena
|
||||||
|
- install -Dm644 com.pena.app.desktop /app/share/applications/com.pena.app.desktop
|
||||||
|
- install -Dm644 icon.png /app/share/icons/hicolor/512x512/apps/com.pena.app.png
|
||||||
|
sources:
|
||||||
|
- type: file
|
||||||
|
path: src-tauri/target/release/pena-taury
|
||||||
|
- type: file
|
||||||
|
path: com.pena.app.desktop
|
||||||
|
- type: file
|
||||||
|
path: src-tauri/icons/icon.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Et le fichier d'intégration au menu **`com.pena.app.desktop`** :
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Pena
|
||||||
|
Comment=Lecteur Markdown
|
||||||
|
Exec=pena
|
||||||
|
Icon=com.pena.app
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;Office;
|
||||||
|
```
|
||||||
|
|
||||||
|
> Point clé sur la portabilité : `finish-args` définit le **sandbox**. Pena n'a besoin que de
|
||||||
|
> l'affichage (Wayland/X11), du GPU (`dri`) et d'un **accès en lecture seule** au `home`
|
||||||
|
> (`--filesystem=home:ro`) pour ouvrir les fichiers `.md`. C'est volontairement minimal.
|
||||||
|
|
||||||
|
### 4.4 — Construire le bundle
|
||||||
|
|
||||||
|
Depuis `Pena-tauri/`, après un `cargo tauri build` réussi :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Construire dans un dépôt OSTree local "repo/"
|
||||||
|
flatpak run org.flatpak.Builder --force-clean --repo=repo \
|
||||||
|
--install-deps-from=flathub build-dir com.pena.app.yml
|
||||||
|
|
||||||
|
# 2. Exporter en UN SEUL fichier .flatpak autoportant
|
||||||
|
flatpak build-bundle repo pena.flatpak com.pena.app \
|
||||||
|
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
```
|
||||||
|
|
||||||
|
On obtient **`pena.flatpak`** : un fichier unique, installable partout par
|
||||||
|
`flatpak install --user pena.flatpak`. L'option `--runtime-repo` y inscrit la référence
|
||||||
|
Flathub, pour que le runtime soit récupéré automatiquement à l'installation s'il manque.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Flatpak : avantages et inconvénients
|
||||||
|
|
||||||
|
### Avantages
|
||||||
|
|
||||||
|
- **Portabilité réelle.** Le runtime fournit WebKitGTK, GTK et une glibc cohérente : les trois
|
||||||
|
blocages du §2 disparaissent. Le même fichier marche sur Fedora, Ubuntu, Arch, Bazzite,
|
||||||
|
Silverblue… sans recompiler.
|
||||||
|
- **Aucune dépendance à installer sur l'hôte.** Idéal pour les systèmes **immuables** (Bazzite,
|
||||||
|
Silverblue) où l'on ne peut pas faire `dnf install webkit2gtk-devel`.
|
||||||
|
- **Installation sans root** (`--user`).
|
||||||
|
- **Sandbox.** L'app n'a accès qu'à ce que `finish-args` autorise (ici : affichage + `home` en
|
||||||
|
lecture seule). Surface d'attaque réduite.
|
||||||
|
- **Intégration de bureau** automatique (entrée de menu, icône) et **mises à jour** par
|
||||||
|
`flatpak update`.
|
||||||
|
- **Reproductible.** Le runtime versionné (`//47`) garantit le même socle partout.
|
||||||
|
|
||||||
|
### Inconvénients
|
||||||
|
|
||||||
|
- **Taille.** Le **runtime** `org.gnome.Platform` pèse plusieurs centaines de Mo. Il n'est
|
||||||
|
téléchargé qu'une fois et partagé entre toutes les apps Flatpak, mais c'est lourd pour une app
|
||||||
|
aussi simple que Pena si c'est le seul Flatpak de la machine.
|
||||||
|
- **Premier lancement / première install** plus longs (téléchargement du runtime).
|
||||||
|
- **Le sandbox demande de la réflexion.** Tout accès fichier hors `home:ro` doit être déclaré
|
||||||
|
explicitement ; un oubli se traduit par une fonctionnalité qui « ne voit pas » les fichiers.
|
||||||
|
- **Dépendance à Flathub** pour récupérer le runtime (réseau requis à l'installation).
|
||||||
|
- **Outillage de build** supplémentaire (`flatpak-builder`, SDK) par rapport à un simple
|
||||||
|
`cargo tauri build`.
|
||||||
|
|
||||||
|
### Quand préférer une autre option
|
||||||
|
|
||||||
|
| Situation | Format conseillé |
|
||||||
|
|---|---|
|
||||||
|
| Diffusion large, multi-distributions, systèmes immuables | **Flatpak** |
|
||||||
|
| Un seul fichier à exécuter sans rien installer | **AppImage** (`bundle.active: true`, cible `appimage`) |
|
||||||
|
| Cible une seule famille (que Debian, ou que Fedora) avec gestion par paquets | **`.deb`** / **`.rpm`** |
|
||||||
|
| Usage perso sur sa propre machine, ou itération de dev | **Binaire brut** / `cargo tauri dev` |
|
||||||
|
|
||||||
|
> **AppImage** est l'autre format « un seul fichier ». Tauri sait en produire (activer le
|
||||||
|
> bundler dans `tauri.conf.json`), mais le **Flatpak** est privilégié ici pour son intégration
|
||||||
|
> au menu, ses mises à jour propres et la gestion automatique des dépendances via Flathub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Liste de contrôle d'une release
|
||||||
|
|
||||||
|
1. Vérifier la version dans `src-tauri/tauri.conf.json` (`"version"`).
|
||||||
|
2. Compiler dans un environnement à **glibc ancienne** si l'on distribue un binaire brut
|
||||||
|
(container Ubuntu 22.04), sinon directement.
|
||||||
|
3. `cargo tauri build` → vérifier `src-tauri/target/release/pena-taury`.
|
||||||
|
4. Construire le `pena.flatpak` (section 4).
|
||||||
|
5. Tester l'install sur une machine **vierge** : `flatpak install --user pena.flatpak` puis
|
||||||
|
`flatpak run com.pena.app`.
|
||||||
|
6. Héberger le `.flatpak` sur **une release Gitea** (URL stable, n'alourdit pas le dépôt) —
|
||||||
|
voir [3.2 §3.4](3.2-Installation-Bazzite.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Build et compilation](3.3-Build.md)
|
||||||
|
- [Installation sur Bazzite OS](3.2-Installation-Bazzite.md) — installation et hébergement du Flatpak
|
||||||
|
- [Installation (Linux générique / Fedora)](3.1-Installation.md)
|
||||||
|
- [Architecture](../2-technique/2.1-Architecture.md)
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# MarkdownRender (CLI `md-render`)
|
||||||
|
|
||||||
|
`MarkdownRender-nodejs` est un **outil en ligne de commande** indépendant de l'application
|
||||||
|
Tauri. Il convertit du Markdown en HTML et propose une prévisualisation live dans le
|
||||||
|
navigateur. Il est utile pour générer rapidement un `.html` ou prévisualiser un document sans
|
||||||
|
lancer Pena.
|
||||||
|
|
||||||
|
> C'est un utilitaire **annexe** ; le produit principal reste
|
||||||
|
> [Pena-tauri](../2-technique/2.1-Architecture.md).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Prérequis : **Node.js** (avec `npm`).
|
||||||
|
|
||||||
|
Depuis `MarkdownRender-nodejs/` :
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm link # rend la commande `md-render` disponible globalement
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Conversion simple (génère un fichier .html à côté de l'entrée)
|
||||||
|
md-render README.md
|
||||||
|
|
||||||
|
# Conversion vers un fichier de sortie précis
|
||||||
|
md-render README.md -o documentation.html
|
||||||
|
|
||||||
|
# Mode preview — ouvre le navigateur sans créer de fichier
|
||||||
|
md-render README.md --preview
|
||||||
|
|
||||||
|
# Preview d'un dossier entier (avec barre latérale)
|
||||||
|
md-render mon-wiki/ --preview
|
||||||
|
|
||||||
|
# Mode watch — sert http://localhost:3000 avec rechargement automatique
|
||||||
|
md-render README.md --watch
|
||||||
|
|
||||||
|
# Sur un port personnalisé
|
||||||
|
md-render README.md --watch --port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Alias | Description | Défaut |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--watch` | `-w` | Démarre un serveur avec rechargement automatique | — |
|
||||||
|
| `--preview` | `-v` | Ouvre dans le navigateur sans créer de fichier | — |
|
||||||
|
| `--port <port>` | `-p` | Port du serveur (watch ou preview) | `3000` |
|
||||||
|
| `--output <fichier>` | `-o` | Fichier HTML de sortie | `<entrée>.html` |
|
||||||
|
| `--help` | `-h` | Affiche l'aide | — |
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
### Mode preview (`--preview`)
|
||||||
|
Le Markdown est converti **en mémoire** et affiché directement dans le navigateur — aucun
|
||||||
|
fichier HTML n'est écrit sur le disque. Le navigateur s'ouvre automatiquement.
|
||||||
|
`Ctrl+C` arrête le serveur. Fonctionne sur un fichier unique ou un dossier entier.
|
||||||
|
|
||||||
|
### Mode watch (`--watch`)
|
||||||
|
Un serveur HTTP local est démarré. À chaque sauvegarde du fichier, la page se recharge
|
||||||
|
automatiquement via **Server-Sent Events (SSE)**. Les assets relatifs (images…) placés dans
|
||||||
|
le même répertoire que le `.md` sont servis automatiquement.
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
|
||||||
|
- 🎨 Coloration syntaxique du code ([highlight.js](https://highlightjs.org/))
|
||||||
|
- 📄 Rendu de style « GitHub-like »
|
||||||
|
- 👁 Prévisualisation instantanée sans fichier (`--preview`)
|
||||||
|
- 🔄 Rechargement automatique en mode watch (SSE)
|
||||||
|
- 🖼 Serveur de fichiers statiques pour les images locales
|
||||||
|
- ⚡ Débounce intelligent pour éviter les rechargements en rafale
|
||||||
|
- 🛡 Protection contre le path traversal
|
||||||
|
|
||||||
|
## Dépendances principales
|
||||||
|
|
||||||
|
| Paquet | Rôle |
|
||||||
|
|---|---|
|
||||||
|
| [`marked`](https://marked.js.org/) | Conversion Markdown → HTML |
|
||||||
|
| `marked-highlight` + [`highlight.js`](https://highlightjs.org/) | Coloration syntaxique |
|
||||||
|
| [`chokidar`](https://github.com/paulmillr/chokidar) | Surveillance de fichiers (mode watch) |
|
||||||
|
| [`minimist`](https://github.com/minimistjs/minimist) | Analyse des arguments CLI |
|
||||||
|
|
||||||
|
## Différences avec Pena-tauri
|
||||||
|
|
||||||
|
| | `md-render` (CLI) | Pena-tauri (app) |
|
||||||
|
|---|---|---|
|
||||||
|
| Plateforme | Terminal + navigateur | Application de bureau |
|
||||||
|
| Rendu Markdown | `marked` (JS) | `comrak` (Rust) |
|
||||||
|
| Coloration | highlight.js | syntect |
|
||||||
|
| Live-reload | ✅ (SSE) | implémenté mais non branché |
|
||||||
|
| Thèmes / CSS éditable | — | ✅ |
|
||||||
|
| Sortie fichier HTML | ✅ | — |
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [Vue d'ensemble de Pena](../1-fonctionnel/1.1-Vue-d-ensemble.md)
|
||||||
|
- [README de la documentation](../README.md)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Documentation Pena
|
||||||
|
|
||||||
|
**Pena** est une application de bureau de lecture de fichiers Markdown, construite avec
|
||||||
|
[Tauri 2](https://tauri.app/) (backend Rust) et du JavaScript vanilla (frontend, sans bundler).
|
||||||
|
Elle permet d'ouvrir un fichier `.md` isolé ou un dossier « wiki » complet, de naviguer entre
|
||||||
|
les documents, et de personnaliser entièrement le rendu via des thèmes et du CSS.
|
||||||
|
|
||||||
|
Le rendu Markdown → HTML est effectué côté Rust avec [`comrak`](https://github.com/kivikakk/comrak)
|
||||||
|
(CommonMark + extensions) et la coloration syntaxique via [`syntect`](https://github.com/trishume/syntect).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sommaire de la documentation
|
||||||
|
|
||||||
|
### Documentation fonctionnelle
|
||||||
|
- [Vue d'ensemble](1-fonctionnel/1.1-Vue-d-ensemble.md) — à quoi sert Pena, pour qui, dans quels cas
|
||||||
|
- [Fonctionnalités](1-fonctionnel/1.2-Fonctionnalites.md) — liste détaillée de tout ce que l'application sait faire
|
||||||
|
- [Interface](1-fonctionnel/1.3-Interface.md) — barre de titre, accueil, lecteur, barre latérale
|
||||||
|
- [Thèmes et personnalisation](1-fonctionnel/1.4-Themes-et-personnalisation.md) — thèmes rapides et éditeur CSS
|
||||||
|
|
||||||
|
### Documentation technique
|
||||||
|
- [Architecture](2-technique/2.1-Architecture.md) — Clean Architecture, découpage Rust et JS
|
||||||
|
- [Commandes Tauri](2-technique/2.2-Commandes-Tauri.md) — référence des commandes exposées au frontend
|
||||||
|
|
||||||
|
### Installation et build
|
||||||
|
- [Installation (Linux générique / Fedora)](3-installation/3.1-Installation.md)
|
||||||
|
- [Installation sur Bazzite OS](3-installation/3.2-Installation-Bazzite.md) — méthode simple : un fichier `.flatpak` pré-construit, hébergé sur Gitea, installable en une commande
|
||||||
|
- [Build et compilation](3-installation/3.3-Build.md)
|
||||||
|
- [Release et distribution](3-installation/3.4-Release-et-distribution.md) — portabilité d'un binaire Linux, formats de distribution, création d'un paquet Flatpak (avantages / inconvénients)
|
||||||
|
|
||||||
|
### Outils annexes
|
||||||
|
- [MarkdownRender (CLI Node.js)](4-outils/4.1-MarkdownRender-CLI.md) — convertisseur `md-render` en ligne de commande
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Composants du dépôt
|
||||||
|
|
||||||
|
| Composant | Techno | Rôle |
|
||||||
|
|---|---|---|
|
||||||
|
| `Pena-tauri/` | Tauri 2 + Rust + JS vanilla | Application de bureau principale (lecteur Markdown) |
|
||||||
|
| `MarkdownRender-nodejs/` | Node.js | Outil CLI annexe `md-render` (conversion + preview live dans le navigateur) |
|
||||||
|
|
||||||
|
> Le cœur du produit est **`Pena-tauri`**. `MarkdownRender-nodejs` est un utilitaire
|
||||||
|
> indépendant, documenté dans [outils/MarkdownRender-CLI.md](4-outils/4.1-MarkdownRender-CLI.md).
|
||||||
@@ -6,17 +6,20 @@
|
|||||||
# 1. `cargo tauri build --bundles deb` -> produit le .deb autonome
|
# 1. `cargo tauri build --bundles deb` -> produit le .deb autonome
|
||||||
# 2. Copie du .deb à côté du manifeste sous le nom `pena.deb`
|
# 2. Copie du .deb à côté du manifeste sous le nom `pena.deb`
|
||||||
# 3. `flatpak-builder` construit le bundle dans la sandbox GNOME
|
# 3. `flatpak-builder` construit le bundle dans la sandbox GNOME
|
||||||
# 4. Installation locale (--user) et/ou export en fichier .flatpak
|
# 4. Installation locale (--user) ET export du fichier pena.flatpak
|
||||||
|
#
|
||||||
|
# Le fichier `pena.flatpak` est TOUJOURS exporté à côté du script.
|
||||||
#
|
#
|
||||||
# Usage :
|
# Usage :
|
||||||
# ./build-flatpak.sh # build + installation locale (--user)
|
# ./build-flatpak.sh # build + installation (--user) + pena.flatpak
|
||||||
# ./build-flatpak.sh --bundle # build + export d'un fichier Pena.flatpak
|
# ./build-flatpak.sh --no-install # build + pena.flatpak seul (pas d'installation)
|
||||||
# ./build-flatpak.sh --no-deb # réutilise le .deb déjà présent (build rapide)
|
# ./build-flatpak.sh --no-deb # réutilise le .deb déjà présent (build rapide)
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
APP_ID="com.pena.app"
|
APP_ID="com.pena.app"
|
||||||
RUNTIME_VERSION="48"
|
RUNTIME_VERSION="48"
|
||||||
|
RUST_CONTAINER="ubuntu-rust" # distrobox contenant cargo-tauri + libs GTK/WebKit
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # .../Pena-tauri
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # .../Pena-tauri
|
||||||
@@ -24,14 +27,16 @@ MANIFEST="$SCRIPT_DIR/${APP_ID}.yml"
|
|||||||
BUILD_DIR="$SCRIPT_DIR/.build" # arbre de build flatpak-builder
|
BUILD_DIR="$SCRIPT_DIR/.build" # arbre de build flatpak-builder
|
||||||
REPO_DIR="$SCRIPT_DIR/.repo" # dépôt ostree temporaire
|
REPO_DIR="$SCRIPT_DIR/.repo" # dépôt ostree temporaire
|
||||||
DEB_TARGET="$SCRIPT_DIR/pena.deb"
|
DEB_TARGET="$SCRIPT_DIR/pena.deb"
|
||||||
|
TAURI_CONF="$PROJECT_DIR/src-tauri/tauri.conf.json"
|
||||||
|
METAINFO="$SCRIPT_DIR/${APP_ID}.metainfo.xml"
|
||||||
|
|
||||||
DO_BUNDLE=0
|
DO_INSTALL=1
|
||||||
BUILD_DEB=1
|
BUILD_DEB=1
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--bundle) DO_BUNDLE=1 ;;
|
--no-install|--bundle) DO_INSTALL=0 ;; # --bundle : alias rétrocompatible
|
||||||
--no-deb) BUILD_DEB=0 ;;
|
--no-deb) BUILD_DEB=0 ;;
|
||||||
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
|
-h|--help) sed -n '2,17p' "$0"; exit 0 ;;
|
||||||
*) echo "Argument inconnu : $arg" >&2; exit 1 ;;
|
*) echo "Argument inconnu : $arg" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -39,26 +44,61 @@ done
|
|||||||
# --- Vérification des outils -------------------------------------------------
|
# --- Vérification des outils -------------------------------------------------
|
||||||
need() { command -v "$1" >/dev/null 2>&1 || { echo "Outil manquant : $1" >&2; exit 1; }; }
|
need() { command -v "$1" >/dev/null 2>&1 || { echo "Outil manquant : $1" >&2; exit 1; }; }
|
||||||
need flatpak
|
need flatpak
|
||||||
if ! command -v flatpak-builder >/dev/null 2>&1; then
|
|
||||||
|
# flatpak-builder : commande native, sinon le Flatpak org.flatpak.Builder
|
||||||
|
# (cas des distributions atomiques type Bazzite/Silverblue).
|
||||||
|
if command -v flatpak-builder >/dev/null 2>&1; then
|
||||||
|
FLATPAK_BUILDER=(flatpak-builder)
|
||||||
|
elif flatpak info org.flatpak.Builder >/dev/null 2>&1; then
|
||||||
|
FLATPAK_BUILDER=(flatpak run org.flatpak.Builder)
|
||||||
|
else
|
||||||
echo "flatpak-builder est requis. Installez-le :" >&2
|
echo "flatpak-builder est requis. Installez-le :" >&2
|
||||||
echo " flatpak install -y flathub org.flatpak.Builder" >&2
|
echo " flatpak install -y flathub org.flatpak.Builder" >&2
|
||||||
echo " (puis lancez ce script via : flatpak run org.flatpak.Builder ...)" >&2
|
echo " ou sur Fedora classique : sudo dnf install flatpak-builder" >&2
|
||||||
echo " ou sur Fedora : sudo dnf install flatpak-builder" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Runtime / SDK GNOME -----------------------------------------------------
|
# --- Remote flathub (scope --user) -------------------------------------------
|
||||||
echo ">> Vérification du runtime GNOME ${RUNTIME_VERSION}…"
|
# flatpak-builder est lancé en --user et installe les dépendances (runtime/SDK
|
||||||
if ! flatpak info "org.gnome.Platform//${RUNTIME_VERSION}" >/dev/null 2>&1; then
|
# via --install-deps-from=flathub) dans cette même installation. Le remote
|
||||||
flatpak install -y --user flathub \
|
# flathub doit donc exister au niveau --user, ce qui n'est pas le cas par
|
||||||
"org.gnome.Platform//${RUNTIME_VERSION}" \
|
# défaut sur les distributions atomiques (où flathub est un remote système).
|
||||||
"org.gnome.Sdk//${RUNTIME_VERSION}"
|
echo ">> Vérification du remote flathub (--user)…"
|
||||||
|
flatpak remote-add --if-not-exists --user flathub \
|
||||||
|
https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
|
||||||
|
# --- 0. Version : source de vérité = tauri.conf.json -------------------------
|
||||||
|
# On lit la version applicative depuis tauri.conf.json et on la répercute dans
|
||||||
|
# le metainfo AppStream (release) afin que le Flatpak porte toujours la
|
||||||
|
# dernière version déclarée dans les sources.
|
||||||
|
need jq
|
||||||
|
VERSION="$(jq -r '.version' "$TAURI_CONF")"
|
||||||
|
if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then
|
||||||
|
echo "Impossible de lire la version dans $TAURI_CONF" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TODAY="$(date -u +%Y-%m-%d)"
|
||||||
|
echo ">> Version applicative : $VERSION (build du $TODAY)"
|
||||||
|
|
||||||
|
# Met à jour (ou insère) l'entrée <release> du metainfo pour cette version.
|
||||||
|
if grep -q "version=\"$VERSION\"" "$METAINFO"; then
|
||||||
|
echo ">> Release $VERSION déjà présente dans le metainfo."
|
||||||
|
else
|
||||||
|
echo ">> Ajout de la release $VERSION au metainfo…"
|
||||||
|
sed -i \
|
||||||
|
"s#<releases>#<releases>\n <release version=\"$VERSION\" date=\"$TODAY\"/>#" \
|
||||||
|
"$METAINFO"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- 1. Build du .deb via Tauri ---------------------------------------------
|
# --- 1. Build du .deb via Tauri ---------------------------------------------
|
||||||
if [[ "$BUILD_DEB" -eq 1 ]]; then
|
if [[ "$BUILD_DEB" -eq 1 ]]; then
|
||||||
echo ">> Build du paquet .deb (cargo tauri build --bundles deb)…"
|
# La compilation Tauri exige les libs de dev GTK/WebKit, absentes de l'hôte
|
||||||
( cd "$PROJECT_DIR" && cargo tauri build --bundles deb )
|
# (distribution atomique). On build donc dans le distrobox `$RUST_CONTAINER`,
|
||||||
|
# qui embarque cargo-tauri et ces libs. flatpak-builder reste sur l'hôte.
|
||||||
|
need distrobox
|
||||||
|
echo ">> Build du paquet .deb dans le distrobox $RUST_CONTAINER…"
|
||||||
|
distrobox enter "$RUST_CONTAINER" -- \
|
||||||
|
bash -lc "cd '$PROJECT_DIR' && cargo tauri build --bundles deb"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DEB_SRC="$(find "$PROJECT_DIR/src-tauri/target/release/bundle/deb" -name '*.deb' 2>/dev/null | sort | tail -n1 || true)"
|
DEB_SRC="$(find "$PROJECT_DIR/src-tauri/target/release/bundle/deb" -name '*.deb' 2>/dev/null | sort | tail -n1 || true)"
|
||||||
@@ -69,24 +109,36 @@ fi
|
|||||||
echo ">> Utilisation du .deb : $DEB_SRC"
|
echo ">> Utilisation du .deb : $DEB_SRC"
|
||||||
cp -f "$DEB_SRC" "$DEB_TARGET"
|
cp -f "$DEB_SRC" "$DEB_TARGET"
|
||||||
|
|
||||||
|
# Icône installée dans le Flatpak (128x128) depuis les sources du projet.
|
||||||
|
cp -f "$PROJECT_DIR/src-tauri/icons/128x128.png" "$SCRIPT_DIR/icon.png"
|
||||||
|
|
||||||
# --- 2. Build Flatpak --------------------------------------------------------
|
# --- 2. Build Flatpak --------------------------------------------------------
|
||||||
|
# Construit l'app dans un dépôt OSTree local ($REPO_DIR) ; les dépendances
|
||||||
|
# manquantes (runtime/SDK) sont récupérées depuis flathub.
|
||||||
echo ">> Construction du Flatpak…"
|
echo ">> Construction du Flatpak…"
|
||||||
rm -rf "$BUILD_DIR"
|
rm -rf "$BUILD_DIR"
|
||||||
FB_ARGS=(--force-clean --user --repo="$REPO_DIR")
|
FB_ARGS=(--force-clean --user --install-deps-from=flathub --repo="$REPO_DIR")
|
||||||
if [[ "$DO_BUNDLE" -eq 0 ]]; then
|
if [[ "$DO_INSTALL" -eq 1 ]]; then
|
||||||
FB_ARGS+=(--install)
|
FB_ARGS+=(--install)
|
||||||
fi
|
fi
|
||||||
flatpak-builder "${FB_ARGS[@]}" "$BUILD_DIR" "$MANIFEST"
|
"${FLATPAK_BUILDER[@]}" "${FB_ARGS[@]}" "$BUILD_DIR" "$MANIFEST"
|
||||||
|
|
||||||
# --- 3. Export d'un fichier .flatpak (optionnel) -----------------------------
|
# --- 3. Export du fichier .flatpak (toujours) --------------------------------
|
||||||
if [[ "$DO_BUNDLE" -eq 1 ]]; then
|
# Le bundle autonome est systématiquement exporté à côté du manifeste.
|
||||||
OUT="$SCRIPT_DIR/Pena.flatpak"
|
OUT="$SCRIPT_DIR/pena.flatpak"
|
||||||
echo ">> Export du bundle : $OUT"
|
echo ">> Export du bundle : $OUT"
|
||||||
flatpak build-bundle "$REPO_DIR" "$OUT" "$APP_ID" "$RUNTIME_VERSION" 2>/dev/null \
|
flatpak build-bundle "$REPO_DIR" "$OUT" "$APP_ID" \
|
||||||
|| flatpak build-bundle "$REPO_DIR" "$OUT" "$APP_ID"
|
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
|
||||||
echo ">> Installer avec : flatpak install --user $OUT"
|
|
||||||
|
echo ">> Nettoyage du dépôt OSTree temporaire…"
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
|
||||||
|
if [[ "$DO_INSTALL" -eq 1 ]]; then
|
||||||
|
echo ">> Pena $VERSION installé (--user) et bundle exporté : $OUT"
|
||||||
|
echo ">> Lancer avec : flatpak run $APP_ID"
|
||||||
else
|
else
|
||||||
echo ">> Installé. Lancer avec : flatpak run $APP_ID"
|
echo ">> Bundle exporté : $OUT"
|
||||||
|
echo ">> Installer avec : flatpak install --user $OUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ">> Terminé."
|
echo ">> Terminé."
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
</categories>
|
</categories>
|
||||||
<content_rating type="oars-1.1"/>
|
<content_rating type="oars-1.1"/>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.1.2" date="2026-07-05"/>
|
||||||
<release version="0.1.0" date="2026-06-23"/>
|
<release version="0.1.0" date="2026-06-23"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ modules:
|
|||||||
path: pena.deb
|
path: pena.deb
|
||||||
- type: file
|
- type: file
|
||||||
path: com.pena.app.metainfo.xml
|
path: com.pena.app.metainfo.xml
|
||||||
|
- type: file
|
||||||
|
path: icon.png
|
||||||
build-commands:
|
build-commands:
|
||||||
- |
|
- |
|
||||||
set -eux
|
set -eux
|
||||||
@@ -40,9 +42,8 @@ modules:
|
|||||||
-e 's/^Icon=.*/Icon=com.pena.app/' \
|
-e 's/^Icon=.*/Icon=com.pena.app/' \
|
||||||
/app/share/applications/com.pena.app.desktop
|
/app/share/applications/com.pena.app.desktop
|
||||||
|
|
||||||
# Icône : on prend la plus grande disponible
|
# Icône (128x128) fournie séparément à côté du manifeste.
|
||||||
icon="$(find usr/share/icons -name '*.png' | sort | tail -n1)"
|
install -Dm644 icon.png /app/share/icons/hicolor/128x128/apps/com.pena.app.png
|
||||||
install -Dm644 "$icon" /app/share/icons/hicolor/128x128/apps/com.pena.app.png
|
|
||||||
|
|
||||||
# Métadonnées AppStream
|
# Métadonnées AppStream
|
||||||
install -Dm644 com.pena.app.metainfo.xml /app/share/metainfo/com.pena.app.metainfo.xml
|
install -Dm644 com.pena.app.metainfo.xml /app/share/metainfo/com.pena.app.metainfo.xml
|
||||||
|
|||||||
|
After Width: | Height: | Size: 6.9 KiB |
@@ -2445,7 +2445,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pena-taury"
|
name = "pena-taury"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comrak",
|
"comrak",
|
||||||
"notify",
|
"notify",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pena-taury"
|
name = "pena-taury"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 928 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Pena",
|
"productName": "Pena",
|
||||||
"version": "0.1.0",
|
"version": "0.1.2",
|
||||||
"identifier": "com.pena.app",
|
"identifier": "com.pena.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
@@ -24,6 +24,12 @@
|
|||||||
"bundle": {
|
"bundle": {
|
||||||
"active": false,
|
"active": false,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"icon": []
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 9.4 KiB |
@@ -22,6 +22,7 @@
|
|||||||
<div id="app-content">
|
<div id="app-content">
|
||||||
|
|
||||||
<div id="view-home">
|
<div id="view-home">
|
||||||
|
<img class="home-logo" src="assets/icon.png" alt="Pena" />
|
||||||
<h1 class="home-title">Pena</h1>
|
<h1 class="home-title">Pena</h1>
|
||||||
<div class="home-buttons">
|
<div class="home-buttons">
|
||||||
<button id="btn-open-file">Ouvrir un fichier Markdown</button>
|
<button id="btn-open-file">Ouvrir un fichier Markdown</button>
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button id="btn-back" class="btn-back">← Accueil</button>
|
<button id="btn-back" class="btn-back">← Accueil</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="sidebar-resizer" class="sidebar__resizer" title="Redimensionner"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<!-- contenu généré par JS -->
|
<!-- contenu généré par JS -->
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { showHome, openPath } from './router.js';
|
import { showHome, openPath, initWatcher } from './router.js';
|
||||||
import { initCssModal } from './ui/css-modal.js';
|
import { initCssModal } from './ui/css-modal.js';
|
||||||
|
import { initSidebarResize } from './ui/sidebar-resize.js';
|
||||||
|
|
||||||
const win = window.__TAURI__.window.getCurrentWindow();
|
const win = window.__TAURI__.window.getCurrentWindow();
|
||||||
|
|
||||||
@@ -33,4 +34,6 @@ document.getElementById('btn-open-dir').addEventListener('click', async () => {
|
|||||||
document.getElementById('btn-back').addEventListener('click', showHome);
|
document.getElementById('btn-back').addEventListener('click', showHome);
|
||||||
|
|
||||||
await initCssModal();
|
await initCssModal();
|
||||||
|
initSidebarResize();
|
||||||
|
await initWatcher();
|
||||||
showHome();
|
showHome();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { appState } from './state/app-state.js';
|
import { appState } from './state/app-state.js';
|
||||||
import { listMdFiles } from './services/files.js';
|
import { listMdFiles } from './services/files.js';
|
||||||
|
import { startWatch, stopWatch, onFileChanged } from './services/watcher.js';
|
||||||
import { renderSidebar } from './ui/sidebar.js';
|
import { renderSidebar } from './ui/sidebar.js';
|
||||||
import { loadPage } from './ui/reader.js';
|
import { loadPage } from './ui/reader.js';
|
||||||
import { saveRecent, renderRecents } from './ui/home.js';
|
import { saveRecent, renderRecents } from './ui/home.js';
|
||||||
@@ -11,9 +12,15 @@ const content = document.querySelector('#content');
|
|||||||
const win = window.__TAURI__.window.getCurrentWindow();
|
const win = window.__TAURI__.window.getCurrentWindow();
|
||||||
const titlebarTitle = document.getElementById('titlebar-title');
|
const titlebarTitle = document.getElementById('titlebar-title');
|
||||||
|
|
||||||
|
const navigate = filePath => loadPage(filePath, appState.currentMode, sidebarEl);
|
||||||
|
|
||||||
export function showHome() {
|
export function showHome() {
|
||||||
|
stopWatch();
|
||||||
appState.currentPath = null;
|
appState.currentPath = null;
|
||||||
appState.currentMode = null;
|
appState.currentMode = null;
|
||||||
|
appState.baseDir = null;
|
||||||
|
appState.currentFile = null;
|
||||||
|
appState.files = [];
|
||||||
sidebarEl.innerHTML = '';
|
sidebarEl.innerHTML = '';
|
||||||
win.setTitle('Pena — Markdown Viewer');
|
win.setTitle('Pena — Markdown Viewer');
|
||||||
titlebarTitle.textContent = 'Pena — Markdown Viewer';
|
titlebarTitle.textContent = 'Pena — Markdown Viewer';
|
||||||
@@ -38,15 +45,21 @@ export async function openPath(path, mode) {
|
|||||||
const parentDir = path.split(sep).slice(0, -1).join(sep);
|
const parentDir = path.split(sep).slice(0, -1).join(sep);
|
||||||
try {
|
try {
|
||||||
const files = await listMdFiles(parentDir);
|
const files = await listMdFiles(parentDir);
|
||||||
|
appState.baseDir = parentDir;
|
||||||
|
appState.files = files;
|
||||||
sidebarEl.classList.remove('hidden');
|
sidebarEl.classList.remove('hidden');
|
||||||
content.style.marginLeft = '';
|
content.style.marginLeft = '';
|
||||||
renderSidebar(parentDir, files, path, filePath => loadPage(filePath, appState.currentMode, sidebarEl));
|
renderSidebar(parentDir, files, path, navigate);
|
||||||
} catch {
|
} catch {
|
||||||
|
// Le dossier parent est illisible : on surveille au moins le fichier ouvert.
|
||||||
|
appState.baseDir = path;
|
||||||
|
appState.files = [];
|
||||||
sidebarEl.innerHTML = '';
|
sidebarEl.innerHTML = '';
|
||||||
sidebarEl.classList.add('hidden');
|
sidebarEl.classList.add('hidden');
|
||||||
content.style.marginLeft = '0';
|
content.style.marginLeft = '0';
|
||||||
}
|
}
|
||||||
await loadPage(path, appState.currentMode, sidebarEl);
|
await loadPage(path, appState.currentMode, sidebarEl);
|
||||||
|
startWatch(appState.baseDir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +69,8 @@ export async function openPath(path, mode) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await listMdFiles(path);
|
const files = await listMdFiles(path);
|
||||||
|
appState.baseDir = path;
|
||||||
|
appState.files = files;
|
||||||
showReader();
|
showReader();
|
||||||
|
|
||||||
const sep = path.includes('\\') ? '\\' : '/';
|
const sep = path.includes('\\') ? '\\' : '/';
|
||||||
@@ -65,10 +80,48 @@ export async function openPath(path, mode) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const firstFile = home ?? files[0];
|
const firstFile = home ?? files[0];
|
||||||
renderSidebar(path, files, firstFile, filePath => loadPage(filePath, appState.currentMode, sidebarEl));
|
renderSidebar(path, files, firstFile, navigate);
|
||||||
await loadPage(firstFile, appState.currentMode, sidebarEl);
|
await loadPage(firstFile, appState.currentMode, sidebarEl);
|
||||||
|
startWatch(path);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showReader();
|
showReader();
|
||||||
content.innerHTML = `<p class="error">Impossible d'ouvrir le dossier : ${err}</p>`;
|
content.innerHTML = `<p class="error">Impossible d'ouvrir le dossier : ${err}</p>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare deux listes de fichiers indépendamment de l'ordre.
|
||||||
|
function sameFiles(a, b) {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
const key = arr => arr.slice().sort().join('\n');
|
||||||
|
return key(a) === key(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réaction à un changement filesystem signalé par le backend (événement débouncé,
|
||||||
|
// filtré sur les fichiers .md).
|
||||||
|
async function handleFileChanged(changedPath) {
|
||||||
|
if (!appState.baseDir || !appState.currentMode) return;
|
||||||
|
|
||||||
|
// Rafraîchir la sidebar uniquement si l'arborescence a changé (ajout/suppression),
|
||||||
|
// pour ne pas replier les dossiers ouverts à chaque simple modification de contenu.
|
||||||
|
try {
|
||||||
|
const files = await listMdFiles(appState.baseDir);
|
||||||
|
if (!sameFiles(files, appState.files)) {
|
||||||
|
appState.files = files;
|
||||||
|
renderSidebar(appState.baseDir, files, appState.currentFile, navigate);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// baseDir supprimé/illisible : on garde la sidebar en l'état.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharger le document affiché si c'est lui qui vient d'être modifié.
|
||||||
|
if (changedPath === appState.currentFile) {
|
||||||
|
await loadPage(appState.currentFile, appState.currentMode, sidebarEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// À appeler une fois au démarrage : enregistre le listener global de changements.
|
||||||
|
export function initWatcher() {
|
||||||
|
return onFileChanged(event => {
|
||||||
|
handleFileChanged(event.payload.path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,12 @@
|
|||||||
export const appState = { currentPath: null, currentMode: null };
|
export const appState = {
|
||||||
|
currentPath: null,
|
||||||
|
currentMode: null,
|
||||||
|
// Dossier réellement surveillé (racine de la sidebar) : le dossier ouvert en
|
||||||
|
// mode « dir », ou le dossier parent du fichier ouvert en mode « file ».
|
||||||
|
baseDir: null,
|
||||||
|
// Fichier actuellement affiché dans le lecteur.
|
||||||
|
currentFile: null,
|
||||||
|
// Dernière liste de fichiers .md connue de `baseDir`, pour détecter les
|
||||||
|
// ajouts/suppressions et ne rafraîchir la sidebar que si l'arborescence change.
|
||||||
|
files: [],
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* ── Reset ── */
|
/* ── Reset ── */
|
||||||
|
:root { --sidebar-width: 264px; }
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
::selection { background: #ffc3c3; }
|
::selection { background: #ffc3c3; }
|
||||||
html, body { height: 100%; margin: 0; }
|
html, body { height: 100%; margin: 0; }
|
||||||
@@ -91,6 +92,13 @@ body {
|
|||||||
background: #1a1b2e;
|
background: #1a1b2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-logo {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.home-title {
|
.home-title {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
@@ -206,7 +214,7 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 36px;
|
top: 36px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 264px;
|
width: var(--sidebar-width);
|
||||||
height: calc(100vh - 36px);
|
height: calc(100vh - 36px);
|
||||||
background: #1a1b2e;
|
background: #1a1b2e;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -215,6 +223,22 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Poignée de redimensionnement */
|
||||||
|
.sidebar__resizer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 6px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
z-index: 100;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.sidebar__resizer:hover,
|
||||||
|
.sidebar__resizer.is-dragging { background: rgba(129, 140, 248, 0.4); }
|
||||||
|
body.is-resizing-sidebar { cursor: col-resize; user-select: none; }
|
||||||
|
|
||||||
.sidebar__scroll {
|
.sidebar__scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -315,10 +339,10 @@ body {
|
|||||||
|
|
||||||
.nav-root-link {
|
.nav-root-link {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 7px 24px;
|
padding: 6px 24px;
|
||||||
color: rgba(255,255,255,0.85);
|
color: rgba(255,255,255,0.85);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
@@ -358,33 +382,32 @@ body {
|
|||||||
.nav-arrow:hover { opacity: 1; background: rgba(255,255,255,0.08); }
|
.nav-arrow:hover { opacity: 1; background: rgba(255,255,255,0.08); }
|
||||||
.nav-folder[open] > summary .nav-arrow { transform: rotate(90deg); opacity: 0.8; }
|
.nav-folder[open] > summary .nav-arrow { transform: rotate(90deg); opacity: 0.8; }
|
||||||
|
|
||||||
|
/* Les libellés de dossiers partagent la même police que les fichiers :
|
||||||
|
même taille (14px), même graisse (400), sans majuscules. Seule la flèche ▶
|
||||||
|
distingue visuellement un dossier d'un fichier. */
|
||||||
.nav-folder-link, .nav-folder-name {
|
.nav-folder-link, .nav-folder-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 6px 24px 6px 0;
|
padding: 6px 24px 6px 0;
|
||||||
font-size: 11px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
text-transform: uppercase;
|
line-height: 1.4;
|
||||||
letter-spacing: 0.08em;
|
color: rgba(255,255,255,0.85);
|
||||||
color: rgba(255,255,255,0.35);
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.nav-folder-link:hover { color: rgba(255,255,255,0.85); }
|
.nav-folder-link:hover { color: #ff5577; }
|
||||||
.nav-folder-link.active { color: #ff5577; }
|
.nav-folder-link.active { color: #ff5577; }
|
||||||
|
|
||||||
|
/* Dossiers imbriqués : même style que les fichiers imbriqués (couleur atténuée). */
|
||||||
.nav-folder .nav-folder > summary .nav-folder-link,
|
.nav-folder .nav-folder > summary .nav-folder-link,
|
||||||
.nav-folder .nav-folder > summary .nav-folder-name {
|
.nav-folder .nav-folder > summary .nav-folder-name {
|
||||||
font-size: 13px;
|
color: rgba(255,255,255,0.55);
|
||||||
font-weight: 500;
|
|
||||||
text-transform: none;
|
|
||||||
letter-spacing: 0;
|
|
||||||
color: rgba(255,255,255,0.45);
|
|
||||||
}
|
}
|
||||||
.nav-folder .nav-folder > summary .nav-folder-link:hover { color: rgba(255,255,255,0.9); }
|
.nav-folder .nav-folder > summary .nav-folder-link:hover { color: #ff5577; }
|
||||||
|
|
||||||
.nav-file-link {
|
.nav-file-link {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -402,16 +425,16 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-depth-1 > .nav-file-link { padding: 5px 24px 5px 52px; }
|
.nav-depth-1 > .nav-file-link { padding: 6px 24px 6px 52px; }
|
||||||
.nav-depth-1 > .nav-folder > summary { padding-left: 24px; }
|
.nav-depth-1 > .nav-folder > summary { padding-left: 24px; }
|
||||||
.nav-depth-2 > .nav-file-link { padding: 4px 24px 4px 68px; }
|
.nav-depth-2 > .nav-file-link { padding: 6px 24px 6px 68px; }
|
||||||
.nav-depth-2 > .nav-folder > summary { padding-left: 40px; }
|
.nav-depth-2 > .nav-folder > summary { padding-left: 40px; }
|
||||||
.nav-depth-3 > .nav-file-link { padding: 4px 24px 4px 84px; }
|
.nav-depth-3 > .nav-file-link { padding: 6px 24px 6px 84px; }
|
||||||
.nav-depth-3 > .nav-folder > summary { padding-left: 56px; }
|
.nav-depth-3 > .nav-folder > summary { padding-left: 56px; }
|
||||||
|
|
||||||
/* ── Content ── */
|
/* ── Content ── */
|
||||||
.content {
|
.content {
|
||||||
margin-left: 264px;
|
margin-left: var(--sidebar-width);
|
||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
padding-right: 72px;
|
padding-right: 72px;
|
||||||
padding-bottom: 96px;
|
padding-bottom: 96px;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { convertFile } from '../services/markdown.js';
|
import { convertFile } from '../services/markdown.js';
|
||||||
|
import { appState } from '../state/app-state.js';
|
||||||
|
|
||||||
const win = window.__TAURI__.window.getCurrentWindow();
|
const win = window.__TAURI__.window.getCurrentWindow();
|
||||||
const titlebarTitle = document.getElementById('titlebar-title');
|
const titlebarTitle = document.getElementById('titlebar-title');
|
||||||
@@ -69,6 +70,7 @@ function addCopyButtons() {
|
|||||||
export async function loadPage(filePath, mode, sidebarEl) {
|
export async function loadPage(filePath, mode, sidebarEl) {
|
||||||
try {
|
try {
|
||||||
const html = await convertFile(filePath);
|
const html = await convertFile(filePath);
|
||||||
|
appState.currentFile = filePath;
|
||||||
content.innerHTML = html;
|
content.innerHTML = html;
|
||||||
addCopyButtons();
|
addCopyButtons();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
const STORAGE_KEY = 'pena.sidebarWidth';
|
||||||
|
const MIN_WIDTH = 180;
|
||||||
|
const MAX_WIDTH = 520;
|
||||||
|
|
||||||
|
function clamp(width) {
|
||||||
|
return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, width));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWidth(width) {
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', `${width}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initSidebarResize() {
|
||||||
|
const handle = document.getElementById('sidebar-resizer');
|
||||||
|
if (!handle) return;
|
||||||
|
|
||||||
|
const stored = parseInt(localStorage.getItem(STORAGE_KEY), 10);
|
||||||
|
if (!Number.isNaN(stored)) applyWidth(clamp(stored));
|
||||||
|
|
||||||
|
let dragging = false;
|
||||||
|
|
||||||
|
const onMove = (e) => {
|
||||||
|
if (!dragging) return;
|
||||||
|
applyWidth(clamp(e.clientX));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUp = () => {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false;
|
||||||
|
handle.classList.remove('is-dragging');
|
||||||
|
document.body.classList.remove('is-resizing-sidebar');
|
||||||
|
const width = parseInt(
|
||||||
|
getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
if (!Number.isNaN(width)) localStorage.setItem(STORAGE_KEY, String(width));
|
||||||
|
window.removeEventListener('mousemove', onMove);
|
||||||
|
window.removeEventListener('mouseup', onUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
handle.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dragging = true;
|
||||||
|
handle.classList.add('is-dragging');
|
||||||
|
document.body.classList.add('is-resizing-sidebar');
|
||||||
|
window.addEventListener('mousemove', onMove);
|
||||||
|
window.addEventListener('mouseup', onUp);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
import { escapeHtml } from '../utils.js';
|
import { escapeHtml } from '../utils.js';
|
||||||
|
|
||||||
|
// Tri « naturel » : les préfixes numériques sont comparés comme des nombres
|
||||||
|
// (10 vient après 9, pas après 1).
|
||||||
|
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
|
||||||
|
function cleanLabel(name) {
|
||||||
|
return name.replace(/\.md$/i, '').replace(/-/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
function buildTree(relPaths, sep) {
|
function buildTree(relPaths, sep) {
|
||||||
const tree = { _files: [], _dirs: {} };
|
const tree = { _files: [], _dirs: {} };
|
||||||
relPaths.forEach((rel) => {
|
relPaths.forEach((rel) => {
|
||||||
@@ -19,8 +27,11 @@ function renderTree(node, prefix, sep, currentRel, depth) {
|
|||||||
let html = '';
|
let html = '';
|
||||||
const depthClass = depth > 0 ? ` class="nav-depth-${Math.min(depth, 3)}"` : '';
|
const depthClass = depth > 0 ? ` class="nav-depth-${Math.min(depth, 3)}"` : '';
|
||||||
|
|
||||||
node._files.slice().sort().forEach((rel) => {
|
node._files
|
||||||
const label = rel.split(sep).pop().replace(/\.md$/i, '').replace(/-/g, ' ');
|
.slice()
|
||||||
|
.sort((a, b) => collator.compare(a.split(sep).pop(), b.split(sep).pop()))
|
||||||
|
.forEach((rel) => {
|
||||||
|
const label = cleanLabel(rel.split(sep).pop());
|
||||||
const abs = prefix + rel;
|
const abs = prefix + rel;
|
||||||
const cls = rel === currentRel ? ' active' : '';
|
const cls = rel === currentRel ? ' active' : '';
|
||||||
if (depth === 0) {
|
if (depth === 0) {
|
||||||
@@ -30,7 +41,7 @@ function renderTree(node, prefix, sep, currentRel, depth) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(node._dirs).sort().forEach((dirName) => {
|
Object.keys(node._dirs).sort((a, b) => collator.compare(a, b)).forEach((dirName) => {
|
||||||
const child = node._dirs[dirName];
|
const child = node._dirs[dirName];
|
||||||
const isOpen = currentRel && currentRel.split(sep).includes(dirName);
|
const isOpen = currentRel && currentRel.split(sep).includes(dirName);
|
||||||
const openAttr = isOpen ? ' open' : '';
|
const openAttr = isOpen ? ' open' : '';
|
||||||
@@ -40,9 +51,9 @@ function renderTree(node, prefix, sep, currentRel, depth) {
|
|||||||
if (homeFile) {
|
if (homeFile) {
|
||||||
const abs = prefix + homeFile;
|
const abs = prefix + homeFile;
|
||||||
const cls = homeFile === currentRel ? ' active' : '';
|
const cls = homeFile === currentRel ? ' active' : '';
|
||||||
folderLabel = `<a href="#" class="nav-folder-link${cls}" data-path="${escapeHtml(abs)}" onclick="event.stopPropagation()">${escapeHtml(dirName)}</a>`;
|
folderLabel = `<a href="#" class="nav-folder-link${cls}" data-path="${escapeHtml(abs)}" onclick="event.stopPropagation()">${escapeHtml(cleanLabel(dirName))}</a>`;
|
||||||
} else {
|
} else {
|
||||||
folderLabel = `<span class="nav-folder-name">${escapeHtml(dirName)}</span>`;
|
folderLabel = `<span class="nav-folder-name">${escapeHtml(cleanLabel(dirName))}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrow = `<span class="nav-arrow" onclick="event.preventDefault();event.stopPropagation();var d=this.closest('details');d.open=!d.open">▶</span>`;
|
const arrow = `<span class="nav-arrow" onclick="event.preventDefault();event.stopPropagation();var d=this.closest('details');d.open=!d.open">▶</span>`;
|
||||||
|
|||||||