1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-02-25 09:18:58 +02:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Henry Heino
c9ab6e0b29 Chore: Fix merge 2026-02-24 16:09:57 -08:00
Henry Heino
288ef2406d Chore: Fix merge 2026-02-24 14:44:47 -08:00
Henry Heino
718d2a4f16 Merge remote-tracking branch 'origin/release-3.5' into dev 2026-02-24 14:21:03 -08:00
Henry Heino
df7a04f552 Desktop: Importing from OneNote: Enable stricter path sanitization on Windows (#14321) 2026-02-10 10:09:34 +00:00
Henry Heino
8ad1dfa2bf Desktop, Cli: Upgrade tar to v7.5.7 (#14313) 2026-02-10 08:41:12 +00:00
Henry Heino
eeaed07a53 Mobile: Fix heading links (#14201) 2026-02-04 10:09:19 +00:00
Henry Heino
f497d898bc Windows: Fixes #14084: .onepkg file import: Fix import failure when notebook titles contain certain Unicode characters (#14090) 2026-01-26 16:56:39 +00:00
15 changed files with 119 additions and 26 deletions

View File

@@ -483,4 +483,17 @@ describe('screens/Note', () => {
await expectToBeEditing(panes.includes('editor'));
expect(store.getState().noteVisiblePanes).toEqual(panes);
});
it('should set the initial editor cursor location to the specified hash', async () => {
await openNewNote({ title: 'To be edited', body: 'a test\n\n# Test\n\n# Test 2\n\n# Test 3' });
store.dispatch({ type: 'NAV_GO', noteHash: 'test-2' });
const { unmount } = render(<WrappedNoteScreen />);
await openEditor();
const editor = await getMarkdownEditorControl();
expect(editor.getCursor().line).toBe(4);
unmount();
});
});

View File

@@ -245,12 +245,16 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
titleContainerWidth: 0,
};
const initialCursorLocation = NotePositionService.instance().getCursorPosition(props.noteId, defaultWindowId).markdown;
if (initialCursorLocation) {
this.selection = { start: initialCursorLocation, end: initialCursorLocation };
}
const initialScroll = NotePositionService.instance().getScrollPercent(props.noteId, defaultWindowId);
this.lastBodyScroll = initialScroll;
const initialCursorLocation = NotePositionService.instance().getCursorPosition(props.noteId, defaultWindowId).markdown;
// Ignore the initial scroll and cursor location when there's a note hash. The editor/viewer should jump to
// the hash, rather than the last position.
if (!props.noteHash) {
if (initialCursorLocation) {
this.selection = { start: initialCursorLocation, end: initialCursorLocation };
}
this.lastBodyScroll = initialScroll;
}
this.titleTextFieldRef = React.createRef();

View File

@@ -97,7 +97,7 @@
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
"string-to-stream": "3.0.1",
"tar": "6.2.1",
"tar": "7.5.7",
"tcp-port-used": "1.0.2",
"uglifycss": "0.0.29",
"url-parse": "1.5.10",

View File

@@ -466,6 +466,7 @@ dependencies = [
"lazy_static",
"parser-macros",
"paste",
"sanitize-filename",
"thiserror",
"uuid",
"wasm-bindgen",
@@ -650,7 +651,6 @@ dependencies = [
"paste",
"percent-encoding",
"regex",
"sanitize-filename",
"thiserror",
"uuid",
"wasm-bindgen",

View File

@@ -11,6 +11,7 @@ widestring = "1.0.2"
uuid = "1.1.2"
lazy_static = "1.4"
wasm-bindgen = "0.2"
sanitize-filename = "0.3.0"
parser-macros = { path = "../parser-macros" }
[dependencies.web-sys]

View File

@@ -48,6 +48,10 @@ function fileReader(path) {
};
}
function isWindows() {
return process.platform === 'win32';
}
module.exports = {
mkdirSyncRecursive,
isDirectory,
@@ -55,4 +59,5 @@ module.exports = {
removePrefix,
normalizeAndWriteFile,
fileReader,
isWindows,
};

View File

@@ -1,9 +1,11 @@
use sanitize_filename::{sanitize_with_options, Options as SanitizeOptions};
use std::io::{Read, Seek};
pub type ApiResult<T> = std::result::Result<T, std::io::Error>;
pub trait FileHandle: Read + Seek {}
pub trait FileApiDriver: Send + Sync {
fn is_windows(&self) -> bool;
fn is_directory(&self, path: &str) -> ApiResult<bool>;
fn read_dir(&self, path: &str) -> ApiResult<Vec<String>>;
fn read_file(&self, path: &str) -> ApiResult<Vec<u8>>;
@@ -23,6 +25,21 @@ pub trait FileApiDriver: Send + Sync {
/// `path_2` is still appended to `path_1`.
fn join(&self, path_1: &str, path_2: &str) -> String;
fn sanitize_file_name(&self, file_name: &str) -> String {
sanitize_with_options(
file_name.trim(),
SanitizeOptions {
// Override "windows". By default, sanitize_filename can
// incorrectly detect the host OS when compiled to WASM.
windows: self.is_windows(),
// Otherwise, match the default sanitize_filename options:
truncate: true,
replacement: "",
},
)
}
/// Splits filename into (base, extension).
fn split_file_name(&self, filename: &str) -> (String, String) {
let ext = self.get_file_extension(filename);

View File

@@ -22,3 +22,28 @@ lazy_static! {
pub fn fs_driver() -> Arc<dyn FileApiDriver> {
FS_DRIVER.clone()
}
#[cfg(test)]
mod test {
use super::fs_driver;
#[test]
fn sanitize_simple() {
assert_eq!(
fs_driver().sanitize_file_name("a.txt"),
"a.txt"
);
assert_eq!(
fs_driver().sanitize_file_name("a/b.txt"),
"ab.txt"
);
assert_eq!(
fs_driver().sanitize_file_name("a\0a/b.txt"),
"aab.txt"
);
assert_eq!(
fs_driver().sanitize_file_name("a\\b\\.txt "),
"ab.txt"
);
}
}

View File

@@ -8,6 +8,16 @@ use std::path::Path;
pub struct FileApiDriverImpl {}
impl FileApiDriver for FileApiDriverImpl {
#[cfg(target_os = "windows")]
fn is_windows(&self) -> bool {
true
}
#[cfg(not(target_os = "windows"))]
fn is_windows(&self) -> bool {
false
}
fn is_directory(&self, path: &str) -> ApiResult<bool> {
let metadata = fs::metadata(path)?;
let file_type = metadata.file_type();

View File

@@ -36,6 +36,9 @@ extern "C" {
#[wasm_bindgen(js_name = fileReader, catch)]
fn open_file_handle(path: &str) -> std::result::Result<JsFileHandle, JsValue>;
#[wasm_bindgen(js_name = isWindows)]
fn is_windows() -> bool;
}
#[wasm_bindgen]
@@ -96,6 +99,10 @@ fn handle_error(error: JsValue, source: &str) -> std::io::Error {
pub struct FileApiDriverImpl {}
impl FileApiDriver for FileApiDriverImpl {
fn is_windows(&self) -> bool {
is_windows()
}
fn is_directory(&self, path: &str) -> ApiResult<bool> {
match is_directory(path) {
Ok(is_dir) => Ok(is_dir),

View File

@@ -17,7 +17,6 @@ mime_guess = "2.0.3"
once_cell = "1.4.1"
palette = "0.5.0"
regex = "1"
sanitize-filename = "0.3.0"
console_error_panic_hook = "0.1.7"
bytes = "1.2.0"
encoding_rs = "0.8.31"

View File

@@ -1,6 +1,5 @@
use color_eyre::eyre::{Result, eyre};
pub use parser::Parser;
use sanitize_filename::sanitize;
use std::{io::Read, panic};
use wasm_bindgen::{JsError, prelude::wasm_bindgen};
@@ -104,12 +103,12 @@ fn convert_onepkg(file_data: Box<dyn FileHandle>, output_dir: &str) -> Result<()
let path_segments_without_filename = &path_segments[0..path_segments.len() - 1];
for part in path_segments_without_filename {
output_path = fs_driver().join(&output_path, &sanitize(part));
output_path = fs_driver().join(&output_path, &fs_driver().sanitize_file_name(part));
fs_driver().make_dir(&output_path)?;
}
let file_name = path_segments.last().unwrap_or(&"");
Ok((output_path, sanitize(file_name)))
Ok((output_path, fs_driver().sanitize_file_name(file_name)))
};
let mut parser = Parser::new();

View File

@@ -38,7 +38,7 @@ impl Renderer {
)?));
}
SectionEntry::SectionGroup(group) => {
let dir_name = sanitize_filename::sanitize(group.display_name());
let dir_name = fs_driver().sanitize_file_name(group.display_name());
let section_group_dir =
fs_driver().join(notebook_dir.as_str(), dir_name.as_str());

View File

@@ -28,7 +28,7 @@ impl Renderer {
pub fn render(&mut self, section: &Section, output_dir: String) -> Result<RenderedSection> {
let section_dir = fs_driver().join(
output_dir.as_str(),
sanitize_filename::sanitize(section.display_name()).as_str(),
fs_driver().sanitize_file_name(section.display_name()).as_str(),
);
log!(
"section_dir: {:?} \n output_dir: {:?}",
@@ -168,7 +168,7 @@ impl Renderer {
let filename = filename_base.trim().replace("/", "_");
let mut i = 0;
let mut current_filename =
sanitize_filename::sanitize(format!("{}{}", filename, extension));
fs_driver().sanitize_file_name(&format!("{}{}", filename, extension));
loop {
let current_full_path = fs_driver().join(parent_dir, &current_filename);
@@ -179,7 +179,7 @@ impl Renderer {
i += 1;
current_filename =
sanitize_filename::sanitize(format!("{}_{}{}", filename, i, extension));
fs_driver().sanitize_file_name(&format!("{}_{}{}", filename, i, extension));
}
Ok(current_filename)

View File

@@ -10942,7 +10942,7 @@ __metadata:
sqlite3: "npm:5.1.6"
string-padding: "npm:1.0.2"
string-to-stream: "npm:3.0.1"
tar: "npm:6.2.1"
tar: "npm:7.5.7"
tcp-port-used: "npm:1.0.2"
tesseract.js: "npm:6.0.1"
typescript: "npm:5.8.3"
@@ -49869,17 +49869,16 @@ __metadata:
languageName: node
linkType: hard
"tar@npm:6.2.1, tar@npm:^6.2.1":
version: 6.2.1
resolution: "tar@npm:6.2.1"
"tar@npm:7.5.7":
version: 7.5.7
resolution: "tar@npm:7.5.7"
dependencies:
chownr: "npm:^2.0.0"
fs-minipass: "npm:^2.0.0"
minipass: "npm:^5.0.0"
minizlib: "npm:^2.1.1"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0
"@isaacs/fs-minipass": "npm:^4.0.0"
chownr: "npm:^3.0.0"
minipass: "npm:^7.1.2"
minizlib: "npm:^3.1.0"
yallist: "npm:^5.0.0"
checksum: 10/0d6938dd32fe5c0f17c8098d92bd9889ee0ed9d11f12381b8146b6e8c87bb5aa49feec7abc42463f0597503d8e89e4c4c0b42bff1a5a38444e918b4878b7fd21
languageName: node
linkType: hard
@@ -49926,6 +49925,20 @@ __metadata:
languageName: node
linkType: hard
"tar@npm:^6.2.1":
version: 6.2.1
resolution: "tar@npm:6.2.1"
dependencies:
chownr: "npm:^2.0.0"
fs-minipass: "npm:^2.0.0"
minipass: "npm:^5.0.0"
minizlib: "npm:^2.1.1"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0
languageName: node
linkType: hard
"tar@npm:^7.4.3":
version: 7.4.3
resolution: "tar@npm:7.4.3"