mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 21:06:22 +08:00
fix(editor): handle footnote reference immediately follow URLs when importing markdown (#12449)
Closes: [BS-3525](https://linear.app/affine-design/issue/BS-3525/markdown-adapter-紧跟着链接的-footnote-reference-会被识别成链接的一部分) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved handling of footnote references that immediately follow URLs in markdown, ensuring correct spacing and parsing. - **Bug Fixes** - Footnote references after URLs are now parsed correctly, preventing formatting issues. - **Tests** - Added comprehensive test suites to verify footnote reference preprocessing and markdown conversion. - **Chores** - Introduced Vitest as a development dependency and added a dedicated test configuration for the footnote module. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { preprocessFootnoteReference } from '../../adapters/markdown/preprocessor';
|
||||
|
||||
describe('FootnoteReferenceMarkdownPreprocessorExtension', () => {
|
||||
it('should add space before footnote reference when it follows a URL', () => {
|
||||
const content = 'https://example.com[^label]';
|
||||
const expected = 'https://example.com [^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should add space before footnote reference when URL has text prefix with space', () => {
|
||||
const content = 'hello world https://example.com[^label]';
|
||||
const expected = 'hello world https://example.com [^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should add space before footnote reference when URL has text prefix with dash', () => {
|
||||
const content = 'text-https://example.com[^label]';
|
||||
const expected = 'text-https://example.com [^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should not add space when footnote reference follows non-URL text', () => {
|
||||
const content = 'normal text[^label]';
|
||||
const expected = 'normal text[^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should not add space when there is already a space before footnote reference', () => {
|
||||
const content = 'https://example.com [^label]';
|
||||
const expected = 'https://example.com [^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle multiple footnote references with mixed URL and non-URL text', () => {
|
||||
const content = 'https://example.com[^1]normal text[^2]http://test.com[^3]';
|
||||
const expected =
|
||||
'https://example.com [^1]normal text[^2]http://test.com [^3]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should not modify footnote definitions', () => {
|
||||
const content = '[^label]: This is a footnote definition';
|
||||
const expected = '[^label]: This is a footnote definition';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle content without footnote references', () => {
|
||||
const content = 'This is a normal text without any footnotes';
|
||||
const expected = 'This is a normal text without any footnotes';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle complex URLs with paths and parameters', () => {
|
||||
const content = 'https://example.com/path?param=value[^label]';
|
||||
const expected = 'https://example.com/path?param=value [^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle invalid URLs', () => {
|
||||
const content = 'not-a-url[^label]';
|
||||
const expected = 'not-a-url[^label]';
|
||||
expect(preprocessFootnoteReference(content)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './markdown/inline-delta';
|
||||
export * from './markdown/markdown-inline';
|
||||
export * from './markdown/preprocessor';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
type MarkdownAdapterPreprocessor,
|
||||
MarkdownPreprocessorExtension,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
/**
|
||||
* Check if a string is a URL
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
function isUrl(str: string): boolean {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess footnote references to avoid markdown link parsing
|
||||
* Only add space when footnote reference follows a URL
|
||||
* @param content
|
||||
* @returns
|
||||
* @example
|
||||
* ```md
|
||||
* https://example.com[^label] -> https://example.com [^label]
|
||||
* normal text[^label] -> normal text[^label]
|
||||
* ```
|
||||
*/
|
||||
export function preprocessFootnoteReference(content: string) {
|
||||
return content.replace(
|
||||
/([^\s]+?)(\[\^[^\]]+\])(?!:)/g,
|
||||
(match, prevText, footnoteRef) => {
|
||||
// Only add space if the previous text is a URL
|
||||
if (isUrl(prevText)) {
|
||||
return prevText + ' ' + footnoteRef;
|
||||
}
|
||||
// Otherwise return the original match
|
||||
return match;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const footnoteReferencePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
name: 'footnote-reference',
|
||||
levels: ['block', 'slice', 'doc'],
|
||||
preprocess: content => {
|
||||
return preprocessFootnoteReference(content);
|
||||
},
|
||||
};
|
||||
|
||||
export const FootnoteReferenceMarkdownPreprocessorExtension =
|
||||
MarkdownPreprocessorExtension(footnoteReferencePreprocessor);
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
|
||||
import {
|
||||
footnoteReferenceDeltaToMarkdownAdapterMatcher,
|
||||
FootnoteReferenceMarkdownPreprocessorExtension,
|
||||
markdownFootnoteReferenceToDeltaMatcher,
|
||||
} from './adapters';
|
||||
|
||||
@@ -15,5 +16,6 @@ export class FootnoteStoreExtension extends StoreExtensionProvider {
|
||||
super.setup(context);
|
||||
context.register(markdownFootnoteReferenceToDeltaMatcher);
|
||||
context.register(footnoteReferenceDeltaToMarkdownAdapterMatcher);
|
||||
context.register(FootnoteReferenceMarkdownPreprocessorExtension);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user