Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2841881a01 | |||
| 8b0f128313 |
@@ -1,2 +1,9 @@
|
||||
src-tauri/target/
|
||||
src-tauri/gen/
|
||||
|
||||
# Artefacts de build Flatpak
|
||||
flatpak/.build/
|
||||
flatpak/.repo/
|
||||
flatpak/.flatpak-builder/
|
||||
flatpak/pena.deb
|
||||
flatpak/*.flatpak
|
||||
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Construit un paquet Flatpak de Pena à partir du .deb généré par Tauri.
|
||||
#
|
||||
# Étapes :
|
||||
# 1. `cargo tauri build --bundles deb` -> produit le .deb autonome
|
||||
# 2. Copie du .deb à côté du manifeste sous le nom `pena.deb`
|
||||
# 3. `flatpak-builder` construit le bundle dans la sandbox GNOME
|
||||
# 4. Installation locale (--user) et/ou export en fichier .flatpak
|
||||
#
|
||||
# Usage :
|
||||
# ./build-flatpak.sh # build + installation locale (--user)
|
||||
# ./build-flatpak.sh --bundle # build + export d'un fichier Pena.flatpak
|
||||
# ./build-flatpak.sh --no-deb # réutilise le .deb déjà présent (build rapide)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
APP_ID="com.pena.app"
|
||||
RUNTIME_VERSION="48"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # .../Pena-tauri
|
||||
MANIFEST="$SCRIPT_DIR/${APP_ID}.yml"
|
||||
BUILD_DIR="$SCRIPT_DIR/.build" # arbre de build flatpak-builder
|
||||
REPO_DIR="$SCRIPT_DIR/.repo" # dépôt ostree temporaire
|
||||
DEB_TARGET="$SCRIPT_DIR/pena.deb"
|
||||
|
||||
DO_BUNDLE=0
|
||||
BUILD_DEB=1
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--bundle) DO_BUNDLE=1 ;;
|
||||
--no-deb) BUILD_DEB=0 ;;
|
||||
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
|
||||
*) echo "Argument inconnu : $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Vérification des outils -------------------------------------------------
|
||||
need() { command -v "$1" >/dev/null 2>&1 || { echo "Outil manquant : $1" >&2; exit 1; }; }
|
||||
need flatpak
|
||||
if ! command -v flatpak-builder >/dev/null 2>&1; then
|
||||
echo "flatpak-builder est requis. Installez-le :" >&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 : sudo dnf install flatpak-builder" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Runtime / SDK GNOME -----------------------------------------------------
|
||||
echo ">> Vérification du runtime GNOME ${RUNTIME_VERSION}…"
|
||||
if ! flatpak info "org.gnome.Platform//${RUNTIME_VERSION}" >/dev/null 2>&1; then
|
||||
flatpak install -y --user flathub \
|
||||
"org.gnome.Platform//${RUNTIME_VERSION}" \
|
||||
"org.gnome.Sdk//${RUNTIME_VERSION}"
|
||||
fi
|
||||
|
||||
# --- 1. Build du .deb via Tauri ---------------------------------------------
|
||||
if [[ "$BUILD_DEB" -eq 1 ]]; then
|
||||
echo ">> Build du paquet .deb (cargo tauri build --bundles deb)…"
|
||||
( cd "$PROJECT_DIR" && cargo tauri build --bundles deb )
|
||||
fi
|
||||
|
||||
DEB_SRC="$(find "$PROJECT_DIR/src-tauri/target/release/bundle/deb" -name '*.deb' 2>/dev/null | sort | tail -n1 || true)"
|
||||
if [[ -z "$DEB_SRC" ]]; then
|
||||
echo "Aucun .deb trouvé. Lancez le script sans --no-deb." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ">> Utilisation du .deb : $DEB_SRC"
|
||||
cp -f "$DEB_SRC" "$DEB_TARGET"
|
||||
|
||||
# --- 2. Build Flatpak --------------------------------------------------------
|
||||
echo ">> Construction du Flatpak…"
|
||||
rm -rf "$BUILD_DIR"
|
||||
FB_ARGS=(--force-clean --user --repo="$REPO_DIR")
|
||||
if [[ "$DO_BUNDLE" -eq 0 ]]; then
|
||||
FB_ARGS+=(--install)
|
||||
fi
|
||||
flatpak-builder "${FB_ARGS[@]}" "$BUILD_DIR" "$MANIFEST"
|
||||
|
||||
# --- 3. Export d'un fichier .flatpak (optionnel) -----------------------------
|
||||
if [[ "$DO_BUNDLE" -eq 1 ]]; then
|
||||
OUT="$SCRIPT_DIR/Pena.flatpak"
|
||||
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"
|
||||
echo ">> Installer avec : flatpak install --user $OUT"
|
||||
else
|
||||
echo ">> Installé. Lancer avec : flatpak run $APP_ID"
|
||||
fi
|
||||
|
||||
echo ">> Terminé."
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.pena.app</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>Pena</name>
|
||||
<summary>Lecteur de fichiers Markdown</summary>
|
||||
<description>
|
||||
<p>
|
||||
Pena est une application de bureau pour lire des fichiers Markdown.
|
||||
La conversion Markdown vers HTML est réalisée côté Rust (comrak), avec
|
||||
coloration syntaxique des blocs de code et plusieurs thèmes de lecture.
|
||||
</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">com.pena.app.desktop</launchable>
|
||||
<categories>
|
||||
<category>Utility</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="0.1.0" date="2026-06-23"/>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -0,0 +1,48 @@
|
||||
id: com.pena.app
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: '48'
|
||||
sdk: org.gnome.Sdk
|
||||
command: pena
|
||||
|
||||
finish-args:
|
||||
# Affichage
|
||||
- --socket=wayland
|
||||
- --socket=fallback-x11
|
||||
- --device=dri
|
||||
- --share=ipc
|
||||
# Lecture des fichiers Markdown ouverts par l'utilisateur
|
||||
- --filesystem=home
|
||||
|
||||
modules:
|
||||
- name: pena
|
||||
buildsystem: simple
|
||||
sources:
|
||||
- type: file
|
||||
path: pena.deb
|
||||
- type: file
|
||||
path: com.pena.app.metainfo.xml
|
||||
build-commands:
|
||||
- |
|
||||
set -eux
|
||||
# Extraction du .deb produit par `cargo tauri build`
|
||||
ar -x pena.deb
|
||||
tar -xf data.tar.gz
|
||||
|
||||
# Binaire principal -> /app/bin/pena (correspond à `command:`)
|
||||
bin="$(find usr/bin -maxdepth 1 -type f | head -n1)"
|
||||
install -Dm755 "$bin" /app/bin/pena
|
||||
|
||||
# Fichier .desktop : renommé en <app-id>.desktop (exigence Flatpak)
|
||||
desktop="$(find usr/share/applications -name '*.desktop' | head -n1)"
|
||||
install -Dm644 "$desktop" /app/share/applications/com.pena.app.desktop
|
||||
sed -i \
|
||||
-e 's/^Exec=.*/Exec=pena/' \
|
||||
-e 's/^Icon=.*/Icon=com.pena.app/' \
|
||||
/app/share/applications/com.pena.app.desktop
|
||||
|
||||
# Icône : on prend la plus grande disponible
|
||||
icon="$(find usr/share/icons -name '*.png' | sort | tail -n1)"
|
||||
install -Dm644 "$icon" /app/share/icons/hicolor/128x128/apps/com.pena.app.png
|
||||
|
||||
# Métadonnées AppStream
|
||||
install -Dm644 com.pena.app.metainfo.xml /app/share/metainfo/com.pena.app.metainfo.xml
|
||||
@@ -17,5 +17,5 @@ tauri-plugin-dialog = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
comrak = { version = "0.28", features = ["syntect"] }
|
||||
syntect = "5"
|
||||
syntect = { version = "5", features = ["yaml-load"] }
|
||||
notify = "7"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -102,13 +102,11 @@ body {
|
||||
.content pre:not(.syntax-highlighting) {
|
||||
background: #0f172a;
|
||||
border: 1px solid #1e293b;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Bloc de code avec coloration syntaxique */
|
||||
.content .syntax-highlighting {
|
||||
border: 1px solid #1e293b;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Texte dans un bloc sans coloration */
|
||||
|
||||
@@ -2,15 +2,30 @@ use comrak::plugins::syntect::SyntectAdapterBuilder;
|
||||
use comrak::{markdown_to_html_with_plugins, Options, Plugins};
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
||||
use syntect::parsing::SyntaxDefinition;
|
||||
|
||||
use crate::domain::MarkdownRenderer;
|
||||
|
||||
const TYPESCRIPT_SYNTAX: &str =
|
||||
include_str!("../../resources/syntaxes/TypeScript.sublime-syntax");
|
||||
|
||||
fn build_syntax_set() -> syntect::parsing::SyntaxSet {
|
||||
let mut builder = syntect::parsing::SyntaxSet::load_defaults_nonewlines().into_builder();
|
||||
if let Ok(def) = SyntaxDefinition::load_from_str(TYPESCRIPT_SYNTAX, true, None) {
|
||||
builder.add(def);
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
pub struct ComrakRenderer;
|
||||
pub struct ComrakPreviewRenderer;
|
||||
|
||||
impl MarkdownRenderer for ComrakRenderer {
|
||||
fn render(&self, content: &str) -> String {
|
||||
let adapter = SyntectAdapterBuilder::new().css().build();
|
||||
let adapter = SyntectAdapterBuilder::new()
|
||||
.syntax_set(build_syntax_set())
|
||||
.css()
|
||||
.build();
|
||||
let mut options = Options::default();
|
||||
options.extension.table = true;
|
||||
options.extension.strikethrough = true;
|
||||
@@ -24,7 +39,10 @@ impl MarkdownRenderer for ComrakRenderer {
|
||||
|
||||
impl MarkdownRenderer for ComrakPreviewRenderer {
|
||||
fn render(&self, content: &str) -> String {
|
||||
let adapter = SyntectAdapterBuilder::new().css().build();
|
||||
let adapter = SyntectAdapterBuilder::new()
|
||||
.syntax_set(build_syntax_set())
|
||||
.css()
|
||||
.build();
|
||||
let options = Options::default();
|
||||
let mut plugins = Plugins::default();
|
||||
plugins.render.codefence_syntax_highlighter = Some(&adapter);
|
||||
@@ -62,6 +80,13 @@ mod tests {
|
||||
assert!(html.contains("<span class="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typescript_syntax_is_highlighted() {
|
||||
let r = ComrakRenderer;
|
||||
let html = r.render("```typescript\ninterface Foo { id: string; }\n```\n");
|
||||
assert!(html.contains("<span class="), "TypeScript should produce highlighted spans");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_css_known_theme_contains_wrapper() {
|
||||
let css = syntax_css_for_theme("base16-ocean.dark");
|
||||
@@ -80,5 +105,4 @@ mod tests {
|
||||
let css = syntax_css_for_theme("nonexistent-theme-xyz");
|
||||
assert!(css.is_empty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 993 B |
@@ -455,6 +455,29 @@ body {
|
||||
.content pre:not(.syntax-highlighting) { background: #f4f4f7; }
|
||||
.content pre code { background: none; padding: 0; font-weight: 400; font-size: 13px; border: none; border-radius: 0; color: inherit; }
|
||||
|
||||
/* ── Bouton « copier » des blocs de code ── */
|
||||
.content .code-block { position: relative; }
|
||||
.content .code-block .copy-code-btn {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 12px;
|
||||
padding: 4px 10px;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
color: #c0c5ce;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
.content .code-block:hover .copy-code-btn,
|
||||
.content .code-block .copy-code-btn:focus-visible { opacity: 1; }
|
||||
.content .code-block .copy-code-btn:hover { background: rgba(255, 255, 255, 0.18); }
|
||||
.content .code-block .copy-code-btn.copied { color: #a3be8c; border-color: rgba(163, 190, 140, 0.5); }
|
||||
|
||||
/* ── Coloration syntaxique (base16-ocean.dark) ── */
|
||||
.syntax-highlighting {
|
||||
color: #c0c5ce;
|
||||
|
||||
@@ -35,10 +35,42 @@ function wireLinks(filePath, mode, sidebarEl) {
|
||||
});
|
||||
}
|
||||
|
||||
function addCopyButtons() {
|
||||
content.querySelectorAll('pre').forEach(pre => {
|
||||
if (pre.parentElement?.classList.contains('code-block')) return;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block';
|
||||
pre.parentNode.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'copy-code-btn';
|
||||
btn.textContent = 'Copier';
|
||||
btn.addEventListener('click', async () => {
|
||||
const code = (pre.querySelector('code') ?? pre).textContent;
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
btn.textContent = 'Copié !';
|
||||
btn.classList.add('copied');
|
||||
} catch {
|
||||
btn.textContent = 'Erreur';
|
||||
}
|
||||
setTimeout(() => {
|
||||
btn.textContent = 'Copier';
|
||||
btn.classList.remove('copied');
|
||||
}, 1500);
|
||||
});
|
||||
wrapper.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPage(filePath, mode, sidebarEl) {
|
||||
try {
|
||||
const html = await convertFile(filePath);
|
||||
content.innerHTML = html;
|
||||
addCopyButtons();
|
||||
|
||||
const basename = filePath.split('/').pop().split('\\').pop();
|
||||
const title = basename.replace(/\.md$/i, '');
|
||||
|
||||
Reference in New Issue
Block a user