fix: parse crlf multibyte headings safely
This commit is contained in:
@@ -25,20 +25,21 @@ pub(super) fn slug_to_title(stem: &str) -> String {
|
||||
/// Returns `None` if no H1 is found on the first non-empty line.
|
||||
pub(super) fn extract_h1_title(content: &str) -> Option<String> {
|
||||
let body = strip_frontmatter(content);
|
||||
for line in body.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(title) = trimmed.strip_prefix("# ") {
|
||||
let title = strip_markdown_chars(title).trim().to_string();
|
||||
if !title.is_empty() {
|
||||
return Some(title);
|
||||
}
|
||||
}
|
||||
break; // first non-empty line is not H1
|
||||
}
|
||||
None
|
||||
let title = first_non_empty_line(body).and_then(markdown_h1_text)?;
|
||||
non_empty_trimmed(&strip_markdown_chars(title)).map(str::to_string)
|
||||
}
|
||||
|
||||
fn non_empty_trimmed(value: &str) -> Option<&str> {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then_some(trimmed)
|
||||
}
|
||||
|
||||
fn first_non_empty_line(value: &str) -> Option<&str> {
|
||||
value.lines().map(str::trim).find(|line| !line.is_empty())
|
||||
}
|
||||
|
||||
fn markdown_h1_text(line: &str) -> Option<&str> {
|
||||
line.strip_prefix("# ").and_then(non_empty_trimmed)
|
||||
}
|
||||
|
||||
/// Extract the display title for a note.
|
||||
@@ -174,16 +175,17 @@ pub(super) fn extract_snippet(content: &str) -> String {
|
||||
}
|
||||
|
||||
fn without_h1_line(s: &str) -> Option<&str> {
|
||||
for (i, line) in s.lines().enumerate() {
|
||||
if line.trim().starts_with("# ") {
|
||||
// Return everything after this line
|
||||
let offset: usize = s.lines().take(i + 1).map(|l| l.len() + 1).sum();
|
||||
return Some(&s[offset.min(s.len())..]);
|
||||
let mut offset = 0;
|
||||
for line in s.split_inclusive('\n') {
|
||||
let trimmed = line.trim_end_matches(['\r', '\n']).trim();
|
||||
if trimmed.starts_with("# ") {
|
||||
return Some(&s[offset + line.len()..]);
|
||||
}
|
||||
// If we hit non-empty non-heading content first, there's no H1 to skip
|
||||
if !line.trim().is_empty() {
|
||||
if !trimmed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
offset += line.len();
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -609,6 +611,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_snippet_crlf_chinese_h1_table_content() {
|
||||
let content =
|
||||
"\r\n\r\n# 上海复盘\r\n\r\n| 指标 | 值 |\r\n| --- | --- |\r\n| 收入 | 增长 |\r\n\r\n正文包含中文字符。";
|
||||
let snippet = extract_snippet(content);
|
||||
|
||||
assert!(snippet.contains("指标"));
|
||||
assert!(snippet.contains("正文包含中文字符"));
|
||||
}
|
||||
|
||||
// --- count_body_words tests ---
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -27,6 +27,11 @@ test.describe('Multibyte note-list search', () => {
|
||||
].join('\n'),
|
||||
'utf-8',
|
||||
)
|
||||
fs.writeFileSync(
|
||||
path.join(noteDir, 'chinese-crlf-table.md'),
|
||||
'\r\n\r\n# 上海复盘\r\n\r\n| 指标 | 值 |\r\n| --- | --- |\r\n| 收入 | 增长 |\r\n\r\n正文包含中文字符。',
|
||||
'utf-8',
|
||||
)
|
||||
await openFixtureVaultDesktopHarness(page, tempVaultDir)
|
||||
})
|
||||
|
||||
@@ -49,4 +54,19 @@ test.describe('Multibyte note-list search', () => {
|
||||
'multibyte-search-boundary',
|
||||
)
|
||||
})
|
||||
|
||||
test('opening CRLF Chinese table content keeps the note searchable @smoke', async ({ page }) => {
|
||||
await showNoteListSearch(page)
|
||||
|
||||
const noteList = page.getByTestId('note-list-container')
|
||||
const searchInput = page.getByPlaceholder('Search notes...')
|
||||
await searchInput.fill('收入')
|
||||
|
||||
await expect(page.getByTestId('note-list-search-loading')).toHaveCount(0)
|
||||
await expect(noteList.getByText('上海复盘', { exact: true })).toBeVisible()
|
||||
|
||||
await noteList.getByText('上海复盘', { exact: true }).click()
|
||||
await expect(page.getByTestId('breadcrumb-filename-trigger')).toContainText('chinese-crlf-table')
|
||||
await expect(page.locator('.editor-content-wrapper').getByText('指标')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user