feat: navigation entre documents via les liens Markdown
Intercepte les clics sur les liens dans le contenu rendu : les liens internes (.md) chargent le document dans l'app, les liens HTTP s'ouvrent dans le navigateur système. Restructure aussi la sidebar avec un footer fixe pour les boutons Personnaliser/Accueil. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+18
-10
@@ -20,6 +20,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="app-content">
|
||||||
|
|
||||||
<div id="view-home">
|
<div id="view-home">
|
||||||
<h1 class="home-title">Pena</h1>
|
<h1 class="home-title">Pena</h1>
|
||||||
<div class="home-buttons">
|
<div class="home-buttons">
|
||||||
@@ -33,21 +35,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="view-reader" class="hidden">
|
<div id="view-reader" class="hidden">
|
||||||
<aside id="sidebar" class="sidebar">
|
<div class="sidebar">
|
||||||
<!-- contenu généré par JS -->
|
<aside id="sidebar" class="sidebar__scroll">
|
||||||
</aside>
|
<!-- contenu généré par JS -->
|
||||||
|
</aside>
|
||||||
|
<div class="sidebar__footer">
|
||||||
|
<button id="btn-customize-css" class="btn-customize-css">
|
||||||
|
<svg class="btn-customize-css__icon" width="15" height="15" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.5 8.5H15.51M10.5 7.5H10.51M7.5 11.5H7.51M12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 13.6569 19.6569 15 18 15H17.4C17.0284 15 16.8426 15 16.6871 15.0246C15.8313 15.1602 15.1602 15.8313 15.0246 16.6871C15 16.8426 15 17.0284 15 17.4V18C15 19.6569 13.6569 21 12 21ZM16 8.5C16 8.77614 15.7761 9 15.5 9C15.2239 9 15 8.77614 15 8.5C15 8.22386 15.2239 8 15.5 8C15.7761 8 16 8.22386 16 8.5ZM11 7.5C11 7.77614 10.7761 8 10.5 8C10.2239 8 10 7.77614 10 7.5C10 7.22386 10.2239 7 10.5 7C10.7761 7 11 7.22386 11 7.5ZM8 11.5C8 11.7761 7.77614 12 7.5 12C7.22386 12 7 11.7761 7 11.5C7 11.2239 7.22386 11 7.5 11C7.77614 11 8 11.2239 8 11.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Personnaliser le CSS
|
||||||
|
</button>
|
||||||
|
<button id="btn-back" class="btn-back">← Accueil</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<!-- contenu généré par JS -->
|
<!-- contenu généré par JS -->
|
||||||
</div>
|
</div>
|
||||||
<button id="btn-customize-css" class="btn-customize-css">
|
|
||||||
<svg class="btn-customize-css__icon" width="15" height="15" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M15.5 8.5H15.51M10.5 7.5H10.51M7.5 11.5H7.51M12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 13.6569 19.6569 15 18 15H17.4C17.0284 15 16.8426 15 16.6871 15.0246C15.8313 15.1602 15.1602 15.8313 15.0246 16.6871C15 16.8426 15 17.0284 15 17.4V18C15 19.6569 13.6569 21 12 21ZM16 8.5C16 8.77614 15.7761 9 15.5 9C15.2239 9 15 8.77614 15 8.5C15 8.22386 15.2239 8 15.5 8C15.7761 8 16 8.22386 16 8.5ZM11 7.5C11 7.77614 10.7761 8 10.5 8C10.2239 8 10 7.77614 10 7.5C10 7.22386 10.2239 7 10.5 7C10.7761 7 11 7.22386 11 7.5ZM8 11.5C8 11.7761 7.77614 12 7.5 12C7.22386 12 7 11.7761 7 11.5C7 11.2239 7.22386 11 7.5 11C7.77614 11 8 11.2239 8 11.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
Personnaliser le CSS
|
|
||||||
</button>
|
|
||||||
<button id="btn-back" class="btn-back">← Accueil</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div><!-- #app-content -->
|
||||||
|
|
||||||
<!-- Modale CSS -->
|
<!-- Modale CSS -->
|
||||||
<div id="css-modal-overlay" class="css-modal-overlay hidden">
|
<div id="css-modal-overlay" class="css-modal-overlay hidden">
|
||||||
<div class="css-modal">
|
<div class="css-modal">
|
||||||
|
|||||||
+35
-18
@@ -8,7 +8,16 @@ body {
|
|||||||
color: #222;
|
color: #222;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
overflow-x: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-content {
|
||||||
|
position: fixed;
|
||||||
|
top: 36px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Titlebar ── */
|
/* ── Titlebar ── */
|
||||||
@@ -78,8 +87,7 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
padding-top: 36px;
|
|
||||||
background: #1a1b2e;
|
background: #1a1b2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +214,23 @@ body {
|
|||||||
z-index: 99;
|
z-index: 99;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar__scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px 0 8px;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar__title-block {
|
.sidebar__title-block {
|
||||||
margin: 32px 24px 8px;
|
margin: 32px 24px 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -233,7 +258,7 @@ body {
|
|||||||
.sidebar__menu {
|
.sidebar__menu {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px 0 48px 0;
|
padding: 8px 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -379,12 +404,12 @@ body {
|
|||||||
/* ── Content ── */
|
/* ── Content ── */
|
||||||
.content {
|
.content {
|
||||||
margin-left: 264px;
|
margin-left: 264px;
|
||||||
padding-top: 84px;
|
padding-top: 48px;
|
||||||
padding-right: 72px;
|
padding-right: 72px;
|
||||||
padding-bottom: 96px;
|
padding-bottom: 96px;
|
||||||
padding-left: 72px;
|
padding-left: 72px;
|
||||||
max-width: 1080px;
|
max-width: 1080px;
|
||||||
min-height: 100vh;
|
min-height: 100%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,11 +484,8 @@ body {
|
|||||||
|
|
||||||
/* ── Customize CSS button ── */
|
/* ── Customize CSS button ── */
|
||||||
.btn-customize-css {
|
.btn-customize-css {
|
||||||
position: fixed;
|
width: calc(100% - 24px);
|
||||||
bottom: 60px;
|
margin: 4px 12px 0;
|
||||||
left: 12px;
|
|
||||||
width: 240px;
|
|
||||||
z-index: 100;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255,255,255,0.7);
|
color: rgba(255,255,255,0.7);
|
||||||
border: 1px solid rgba(255,255,255,0.15);
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
@@ -626,11 +648,7 @@ body {
|
|||||||
|
|
||||||
/* ── Back button ── */
|
/* ── Back button ── */
|
||||||
.btn-back {
|
.btn-back {
|
||||||
position: fixed;
|
width: 100%;
|
||||||
bottom: 24px;
|
|
||||||
left: 0;
|
|
||||||
width: 264px;
|
|
||||||
z-index: 98;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255,255,255,0.45);
|
color: rgba(255,255,255,0.45);
|
||||||
border: none;
|
border: none;
|
||||||
@@ -641,7 +659,6 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
z-index: 100;
|
|
||||||
}
|
}
|
||||||
.btn-back:hover { color: #ff5577; }
|
.btn-back:hover { color: #ff5577; }
|
||||||
|
|
||||||
@@ -651,7 +668,7 @@ body {
|
|||||||
left: -300px;
|
left: -300px;
|
||||||
transition: left 0.2s cubic-bezier(0.09, 0.46, 0.45, 0.94);
|
transition: left 0.2s cubic-bezier(0.09, 0.46, 0.45, 0.94);
|
||||||
}
|
}
|
||||||
.content { margin-left: 0; padding: 68px 24px 64px; }
|
.content { margin-left: 0; padding: 32px 24px 64px; }
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 540px) {
|
@media screen and (max-width: 540px) {
|
||||||
.content { padding: 68px 16px 48px; }
|
.content { padding: 68px 16px 48px; }
|
||||||
|
|||||||
@@ -4,6 +4,37 @@ const win = window.__TAURI__.window.getCurrentWindow();
|
|||||||
const titlebarTitle = document.getElementById('titlebar-title');
|
const titlebarTitle = document.getElementById('titlebar-title');
|
||||||
const content = document.querySelector('#content');
|
const content = document.querySelector('#content');
|
||||||
|
|
||||||
|
function resolveRelativePath(currentFile, href) {
|
||||||
|
const sep = currentFile.includes('\\') ? '\\' : '/';
|
||||||
|
const dir = currentFile.split(sep).slice(0, -1).join(sep);
|
||||||
|
const parts = [...dir.split(sep), ...href.split('/')];
|
||||||
|
const resolved = [];
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part === '..') resolved.pop();
|
||||||
|
else if (part !== '.') resolved.push(part);
|
||||||
|
}
|
||||||
|
return resolved.join(sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireLinks(filePath, mode, sidebarEl) {
|
||||||
|
content.querySelectorAll('a[href]').forEach(a => {
|
||||||
|
const href = a.getAttribute('href');
|
||||||
|
if (!href || href.startsWith('#')) return;
|
||||||
|
|
||||||
|
if (/^https?:\/\//.test(href)) {
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.setAttribute('rel', 'noopener noreferrer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.addEventListener('click', async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const resolved = resolveRelativePath(filePath, href);
|
||||||
|
await loadPage(resolved, mode, sidebarEl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -27,6 +58,8 @@ export async function loadPage(filePath, mode, sidebarEl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wireLinks(filePath, mode, sidebarEl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
content.innerHTML = `<p class="error">Impossible de charger le fichier : ${err}</p>`;
|
content.innerHTML = `<p class="error">Impossible de charger le fichier : ${err}</p>`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user