diff --git a/src/transform/plugins/includes/collect.ts b/src/transform/plugins/includes/collect.ts index 1b5f16d7..72a62dce 100644 --- a/src/transform/plugins/includes/collect.ts +++ b/src/transform/plugins/includes/collect.ts @@ -1,7 +1,7 @@ import {relative} from 'path'; import {bold} from 'chalk'; -import {resolveRelativePath} from '../../utilsFS'; +import {isFileExists, resolveRelativePath} from '../../utilsFS'; import {MarkdownItPluginOpts} from '../typings'; const includesPaths: string[] = []; @@ -23,9 +23,13 @@ const collect = (input: string, options: Opts) => { let [, , , relativePath] = match; const [matchedInclude] = match; - relativePath = relativePath.split('#')[0]; + let includePath = resolveRelativePath(path, relativePath); + const hashIndex = relativePath.lastIndexOf('#'); + if (hashIndex > -1 && !isFileExists(includePath)) { + includePath = includePath.slice(0, includePath.lastIndexOf('#')); + relativePath = relativePath.slice(0, hashIndex); + } - const includePath = resolveRelativePath(path, relativePath); const targetDestPath = resolveRelativePath(destPath, relativePath); if (includesPaths.includes(includePath)) { diff --git a/src/transform/plugins/includes/index.ts b/src/transform/plugins/includes/index.ts index c3732c31..534854be 100644 --- a/src/transform/plugins/includes/index.ts +++ b/src/transform/plugins/includes/index.ts @@ -1,6 +1,6 @@ import {bold} from 'chalk'; -import {getFileTokens, GetFileTokensOpts, getFullIncludePath} from '../../utilsFS'; +import {getFileTokens, GetFileTokensOpts, getFullIncludePath, isFileExists} from '../../utilsFS'; import {findBlockTokens} from '../../utils'; import Token from 'markdown-it/lib/token'; import {MarkdownItPluginCb, MarkdownItPluginOpts} from '../typings'; @@ -39,8 +39,15 @@ function unfoldIncludes(state: StateCore, path: string, options: Options) { ) { try { const [, keyword /* description */, , includePath] = match; + const fullIncludePath = getFullIncludePath(includePath, root, path); - const [pathname, hash] = fullIncludePath.split('#'); + let pathname = fullIncludePath; + let hash = ''; + const hashIndex = fullIncludePath.lastIndexOf('#'); + if (hashIndex > -1 && !isFileExists(pathname)) { + pathname = fullIncludePath.slice(0, hashIndex); + hash = fullIncludePath.slice(hashIndex + 1); + } if (!pathname.startsWith(root)) { i++; diff --git a/test/anchors.test.ts b/test/anchors.test.ts index e1c6f78b..936244c1 100644 --- a/test/anchors.test.ts +++ b/test/anchors.test.ts @@ -94,4 +94,21 @@ describe('Anchors', () => { '
Content
\n', ); }); + + it('should include content by anchor in sharped path file', () => { + expect( + transformYfm( + 'Content before include\n' + + '\n' + + '{% include [file](./mocks/folder-with-#-sharp/file-with-#-sharp.md#anchor) %}\n' + + '\n' + + 'After include', + ), + ).toBe( + 'Content before include
\n' + + 'Subcontent
\n' + + 'After include
\n', + ); + }); }); diff --git a/test/data/includes.ts b/test/data/includes.ts index 58e52c8b..f0cb5401 100644 --- a/test/data/includes.ts +++ b/test/data/includes.ts @@ -568,3 +568,372 @@ export const codeInBackQuote = [ hidden: false, }, ]; + +export const sharpedFile = [ + { + type: 'paragraph_open', + tag: 'p', + attrs: null, + map: [0, 1], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [0, 1], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'Text before include', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'Text before include', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_close', + tag: 'p', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'heading_open', + tag: 'h1', + attrs: null, + map: [0, 1], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '#', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [0, 1], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'Title', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'Title', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'heading_close', + tag: 'h1', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '#', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_open', + tag: 'p', + attrs: null, + map: [2, 3], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [2, 3], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'Content', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'Content', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_close', + tag: 'p', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'heading_open', + tag: 'h2', + attrs: null, + map: [4, 5], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '##', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [4, 5], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'Subtitle {#anchor}', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'Subtitle {#anchor}', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'heading_close', + tag: 'h2', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '##', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_open', + tag: 'p', + attrs: null, + map: [6, 7], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [6, 7], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'Subcontent', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'Subcontent', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_close', + tag: 'p', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_open', + tag: 'p', + attrs: null, + map: [4, 5], + nesting: 1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'inline', + tag: '', + attrs: null, + map: [4, 5], + nesting: 0, + level: 1, + children: [ + { + type: 'text', + tag: '', + attrs: null, + map: null, + nesting: 0, + level: 0, + children: null, + content: 'After include', + markup: '', + info: '', + meta: null, + block: false, + hidden: false, + }, + ], + content: 'After include', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, + { + type: 'paragraph_close', + tag: 'p', + attrs: null, + map: null, + nesting: -1, + level: 0, + children: null, + content: '', + markup: '', + info: '', + meta: null, + block: true, + hidden: false, + }, +]; diff --git a/test/includes.test.ts b/test/includes.test.ts index def72c65..ebe9f9fe 100644 --- a/test/includes.test.ts +++ b/test/includes.test.ts @@ -3,7 +3,7 @@ import {dirname} from 'path'; import includes from '../src/transform/plugins/includes'; import yfmlint from '../src/transform/yfmlint'; import {callPlugin, tokenize} from './utils'; -import {title, notitle, codeInBackQuote} from './data/includes'; +import {title, notitle, codeInBackQuote, sharpedFile} from './data/includes'; import {log} from '../src/transform/log'; describe('Includes', () => { @@ -130,4 +130,25 @@ describe('Includes', () => { expect(expectedCondition).toEqual(true); }); + + test('Should include file with sharped path', () => { + const mocksPath = require.resolve('./utils.ts'); + + const result = callPlugin( + includes, + tokenize([ + 'Text before include', + '', + '{% include [file](./mocks/folder-with-#-sharp/file-with-#-sharp.md) %}', + '', + 'After include', + ]), + { + path: mocksPath, + root: dirname(mocksPath), + }, + ); + + expect(result).toEqual(sharpedFile); + }); }); diff --git a/test/mocks/folder-with-#-sharp/file-with-#-sharp.md b/test/mocks/folder-with-#-sharp/file-with-#-sharp.md new file mode 100644 index 00000000..32b8da08 --- /dev/null +++ b/test/mocks/folder-with-#-sharp/file-with-#-sharp.md @@ -0,0 +1,7 @@ +# Title + +Content + +## Subtitle {#anchor} + +Subcontent \ No newline at end of file