Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.11.1](https://github.com/ekino/MarkdownViewer/releases/tag/v0.11.1) - 2026-08-13

### Added

- General preference for what opening a document from Finder does when a document is already open: hand it to a new window (default, matching macOS document viewers) or replace the current window's content. A window still on the welcome screen is reused either way, so choosing "new window" never leaves an empty window behind (FR + EN)

### Fixed

- Open documents from folders outside the home directory. Since 0.11.0 the sidebar listed files under `/tmp`, `/Volumes` or a repo cloned outside home, but selecting one did nothing: the listing and the read did not share the same access rule. Reads are now confined to the folder you opened — the access you actually granted — which also stops a crafted relative link from escaping it
- Show a message when a document cannot be opened. The failure was logged to the console only, so an unopenable file looked like an unresponsive click (FR + EN)

## [0.11.0](https://github.com/ekino/MarkdownViewer/releases/tag/v0.11.0) - 2026-08-13

### Added
Expand Down
37 changes: 37 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,22 @@
line-height: 1.45;
}
#slow-read-notice[hidden] { display: none; }

/* Same shape as the slow-read notice, in an error tone. */
#load-error {
display: flex;
align-items: flex-start;
gap: 10px;
margin: 0 0 24px;
padding: 10px 14px;
border: 1px solid var(--border);
border-left: 3px solid #dc2626;
border-radius: 6px;
color: var(--text);
font-size: 13px;
line-height: 1.45;
}
#load-error[hidden] { display: none; }
#slow-read-notice .srn-text { flex: 1; }
#slow-read-notice .srn-close {
flex: none;
Expand Down Expand Up @@ -2049,6 +2065,9 @@ <h2 data-i18n="app.name">Markdown Viewer</h2>
</button>
</div>
</div>
<div id="load-error" hidden>
<span class="srn-text" id="load-error-text"></span>
</div>
<div id="slow-read-notice" hidden>
<span class="srn-text" data-i18n="slowRead.message"></span>
<button
Expand Down Expand Up @@ -2450,6 +2469,24 @@ <h2 data-i18n="app.name">Markdown Viewer</h2>
</button>
</div>
</div>
<div class="prefs-row">
<div class="prefs-label">
<span class="prefs-label-title" data-i18n="prefs.general.finderOpen.title">
Ouverture depuis le Finder
</span>
<span class="prefs-label-hint" data-i18n="prefs.general.finderOpen.hint">
Quand un document est déjà ouvert
</span>
</div>
<div class="prefs-segment" id="prefs-finder-open" role="radiogroup">
<button type="button" data-value="new-window" data-i18n="prefs.finderOpen.newWindow">
Nouvelle fenêtre
</button>
<button type="button" data-value="reuse" data-i18n="prefs.finderOpen.reuse">
Fenêtre actuelle
</button>
</div>
</div>
</div>
</div>
<div class="prefs-footer">
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mdv"
version = "0.11.0"
version = "0.11.1"
description = "A Tauri App"
authors = ["you"]
license = ""
Expand Down
27 changes: 20 additions & 7 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,20 +535,33 @@ async fn document_mtime(path: String) -> Result<u64, String> {
// is ~1ms). A sync command would run on the main thread and freeze the UI; an
// async command runs off it, so the window stays responsive during the read.
//
// Confined to the user's home dir or the bundled resource dir (examples):
// canonicalize resolves `..`/symlinks, then we require the result to sit under
// an allowed root. Errors stay generic to avoid leaking paths.
// Confined to the folder the user opened, plus the bundled resource dir for the
// built-in examples. `root` is the folder the window is currently browsing, so
// a document can only ever pull in files the user already granted access to by
// opening that folder — a crafted relative link cannot escape it.
//
// Confining to the *home* dir instead (as this did until 0.11.1) got the threat
// model backwards: it allowed ~/.ssh and ~/.aws while refusing every folder
// outside home, so opening anything under /tmp, /Volumes or a repo cloned
// elsewhere failed even though the sidebar listed it fine.
//
// canonicalize resolves `..`/symlinks on both sides before comparing. Errors
// stay generic to avoid leaking paths.
#[tauri::command]
async fn read_document(app: tauri::AppHandle, path: String) -> Result<String, String> {
async fn read_document(
app: tauri::AppHandle,
path: String,
root: String,
) -> Result<String, String> {
let requested =
std::fs::canonicalize(&path).map_err(|_| "cannot resolve path".to_string())?;

let roots = [app.path().home_dir(), app.path().resource_dir()]
let roots = [std::fs::canonicalize(&root).ok(), app.path().resource_dir().ok()]
.into_iter()
.flatten()
.filter_map(|p| std::fs::canonicalize(p).ok());
if !roots.into_iter().any(|root| requested.starts_with(&root)) {
return Err("path outside allowed roots".to_string());
if !roots.into_iter().any(|allowed| requested.starts_with(&allowed)) {
return Err("path outside the opened folder".to_string());
}

let t = std::time::Instant::now();
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Markdown Viewer",
"version": "0.11.0",
"version": "0.11.1",
"identifier": "com.mdv.viewer",
"build": {
"frontendDist": "../dist",
Expand Down
15 changes: 6 additions & 9 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,33 @@
"titlebar.print": "Print",
"titlebar.theme": "Toggle dark mode",
"titlebar.preferences": "Preferences",

"search.placeholder": "Search…",
"search.prev": "Previous (Shift+Cmd+G)",
"search.next": "Next (Cmd+G)",
"search.options": "Search options",
"search.opt.case": "Case sensitive (Aa)",
"search.opt.diacritics": "Ignore accents",
"search.opt.wholeword": "Whole word",

"app.name": "Markdown Viewer",
"empty.subtitle": "Open a folder to browse your documentation.",
"empty.open": "Open folder",
"empty.examples": "View examples",
"outline.title": "On this page",
"breadcrumb.change": "Change folder",
"sidebar.parent": "Parent directory",

"prefs.title": "Preferences",
"prefs.close": "Close (Esc)",
"prefs.done": "Done",
"prefs.tab.general": "General",
"prefs.tab.fonts": "Fonts",
"prefs.tab.appearance": "Appearance",

"prefs.general.language.title": "Language",
"prefs.general.language.hint": "Interface language",
"prefs.general.outline.title": "Outline panel",
"prefs.general.outline.hint": "Right-side table of contents",
"prefs.outline.auto": "Auto",
"prefs.outline.always": "Always",
"prefs.outline.hidden": "Hidden",

"prefs.fonts.size.title": "Size",
"prefs.fonts.size.hint": "Quick preset for document text",
"prefs.fonts.size.small": "Small",
Expand All @@ -59,7 +54,6 @@
"prefs.fonts.size.exact.hint": "Overrides the preset (10–32 px)",
"prefs.fonts.system.default": "System default",
"prefs.fonts.placeholder.auto": "auto",

"prefs.appearance.follow": "Follow system",
"prefs.appearance.follow.hint": "Automatically switch between the light and dark variant of the theme based on the macOS appearance.",
"prefs.appearance.new": "New theme…",
Expand All @@ -74,7 +68,6 @@
"prefs.appearance.duplicate": "Duplicate",
"prefs.appearance.edit": "Edit",
"prefs.appearance.custom.badge": "Custom",

"editor.title": "Theme editor",
"editor.back": "← Back",
"editor.default.name": "My theme",
Expand Down Expand Up @@ -103,11 +96,15 @@
"editor.var.table-stripe": "Table stripe",
"editor.var.search-hit": "Search highlight",
"editor.var.search-hit-active": "Active search hit",

"mermaid.error": "Mermaid syntax error — showing source",
"mermaid.close": "Close (Esc)",
"code.copy": "Copy",
"code.copied": "Copied!",
"slowRead.message": "This file took a long time to open. This folder looks cloud-synced (OneDrive, iCloud…): \"online-only\" files are downloaded on first read. Move your documents to a local folder to speed things up.",
"slowRead.dismiss": "Dismiss"
"slowRead.dismiss": "Dismiss",
"loadError.message": "Could not open {file}. The file may have been moved, or it sits outside the folder you opened.",
"prefs.general.finderOpen.title": "Opening from Finder",
"prefs.general.finderOpen.hint": "When a document is already open",
"prefs.finderOpen.newWindow": "New window",
"prefs.finderOpen.reuse": "Current window"
}
15 changes: 6 additions & 9 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,33 @@
"titlebar.print": "Imprimer",
"titlebar.theme": "Basculer mode sombre",
"titlebar.preferences": "Préférences",

"search.placeholder": "Rechercher…",
"search.prev": "Précédent (Shift+Cmd+G)",
"search.next": "Suivant (Cmd+G)",
"search.options": "Options de recherche",
"search.opt.case": "Sensible à la casse (Aa)",
"search.opt.diacritics": "Ignorer les accents",
"search.opt.wholeword": "Mot entier",

"app.name": "Markdown Viewer",
"empty.subtitle": "Ouvrez un dossier pour parcourir votre documentation.",
"empty.open": "Ouvrir un dossier",
"empty.examples": "Voir les exemples",
"outline.title": "Sur cette page",
"breadcrumb.change": "Changer de dossier",
"sidebar.parent": "Dossier parent",

"prefs.title": "Préférences",
"prefs.close": "Fermer (Échap)",
"prefs.done": "Terminé",
"prefs.tab.general": "Général",
"prefs.tab.fonts": "Polices",
"prefs.tab.appearance": "Apparence",

"prefs.general.language.title": "Langue",
"prefs.general.language.hint": "Langue de l'interface",
"prefs.general.outline.title": "Panneau de plan",
"prefs.general.outline.hint": "Table des matières à droite",
"prefs.outline.auto": "Auto",
"prefs.outline.always": "Toujours",
"prefs.outline.hidden": "Caché",

"prefs.fonts.size.title": "Taille",
"prefs.fonts.size.hint": "Préréglage rapide pour le texte du document",
"prefs.fonts.size.small": "Petit",
Expand All @@ -59,7 +54,6 @@
"prefs.fonts.size.exact.hint": "Surcharge le préréglage (10–32 px)",
"prefs.fonts.system.default": "Par défaut système",
"prefs.fonts.placeholder.auto": "auto",

"prefs.appearance.follow": "Suivre le système",
"prefs.appearance.follow.hint": "Bascule automatiquement entre la variante claire et sombre du thème selon l'apparence macOS.",
"prefs.appearance.new": "Nouveau thème…",
Expand All @@ -74,7 +68,6 @@
"prefs.appearance.duplicate": "Dupliquer",
"prefs.appearance.edit": "Éditer",
"prefs.appearance.custom.badge": "Personnalisé",

"editor.title": "Éditeur de thème",
"editor.back": "← Retour",
"editor.default.name": "Mon thème",
Expand Down Expand Up @@ -103,11 +96,15 @@
"editor.var.table-stripe": "Rayure de tableau",
"editor.var.search-hit": "Résultat de recherche",
"editor.var.search-hit-active": "Résultat actif",

"mermaid.error": "Erreur de syntaxe Mermaid — affichage de la source",
"mermaid.close": "Fermer (Échap)",
"code.copy": "Copier",
"code.copied": "Copié !",
"slowRead.message": "Ce fichier a mis longtemps à s'ouvrir. Ce dossier semble synchronisé dans le cloud (OneDrive, iCloud…) : les fichiers « en ligne uniquement » se téléchargent à la première lecture. Déplacez vos documents dans un dossier local pour accélérer.",
"slowRead.dismiss": "Masquer"
"slowRead.dismiss": "Masquer",
"loadError.message": "Impossible d'ouvrir {file}. Le fichier a peut-être été déplacé, ou il se trouve hors du dossier que vous avez ouvert.",
"prefs.general.finderOpen.title": "Ouverture depuis le Finder",
"prefs.general.finderOpen.hint": "Quand un document est déjà ouvert",
"prefs.finderOpen.newWindow": "Nouvelle fenêtre",
"prefs.finderOpen.reuse": "Fenêtre actuelle"
}
Loading
Loading