Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/embed-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Sometimes you don't want to embed a whole file. Maybe because you need just a fe
```

In your code file you need to surround the fragment between `/// [demo]` lines (before and after the fragment).
Alternatively you can use `### [demo]`.
Alternatively you can use `### [demo]`. If you want the full line containing the fragment identifier you can add the option `:fragmentFullLine`.

Example:

Expand Down
1 change: 1 addition & 0 deletions src/core/render/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class Compiler {
}

embed.fragment = config.fragment;
embed.fragmentFullLine = config.fragmentFullLine;

return embed;
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/render/compiler/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export const compileMedia = {
},
video(url, title) {
return {
html: `<video src="${url}" ${title || 'controls'}>Not Support</video>`,
html: `<video src="${url}" ${title || 'controls'}>Not Supported</video>`,
};
},
audio(url, title) {
return {
html: `<audio src="${url}" ${title || 'controls'}>Not Support</audio>`,
html: `<audio src="${url}" ${title || 'controls'}>Not Supported</audio>`,
};
},
code(url, title) {
Expand Down
28 changes: 21 additions & 7 deletions src/core/render/embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ const cached = {};
*
* @param {string} text - The input text that may contain embedded fragments.
* @param {string} fragment - The fragment identifier to search for.
* @returns {string} - The extracted and demented content, or an empty string if not found.
* @param {boolean} fullLine - Boolean flag to enable full-line matching of fragment identifiers.
* @returns {string} - The extracted and dedented content, or an empty string if not found.
*/
function extractFragmentContent(text, fragment) {
function extractFragmentContent(text, fragment, fullLine) {
if (!fragment) {
return text;
}

let fragmentRegex = `(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`;
const contentRegex = `[\\s\\S]*?`;
if (fullLine) {
// Match full line for fragment
fragmentRegex = `.*${fragmentRegex}.*\n`;
}
const pattern = new RegExp(
`(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]([\\s\\S]*?)(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`,
);
`(?:${fragmentRegex})(${contentRegex})(?:${fragmentRegex})`,
); // content is the capture group
const match = text.match(pattern);
return stripIndent((match || [])[1] || '').trim();
}
Expand Down Expand Up @@ -68,13 +74,21 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
}

if (currentToken.embed.fragment) {
text = extractFragmentContent(text, currentToken.embed.fragment);
text = extractFragmentContent(
text,
currentToken.embed.fragment,
currentToken.embed.fragmentFullLine,
);
}

embedToken = compile.lexer(text);
} else if (currentToken.embed.type === 'code') {
if (currentToken.embed.fragment) {
text = extractFragmentContent(text, currentToken.embed.fragment);
text = extractFragmentContent(
text,
currentToken.embed.fragment,
currentToken.embed.fragmentFullLine,
);
}

embedToken = compile.lexer(
Expand Down
55 changes: 54 additions & 1 deletion test/integration/example.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,48 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
);
});

test('embed file full line fragment identifier', async () => {
await docsifyInit({
markdown: {
homepage: `
# Embed Test
[filename](_media/example1.html ':include :type=code :fragment=demo :fragmentFullLine')
`,
},
routes: {
'_media/example1.html': `
<script>
let myURL = 'https://api.example.com/data';
/// [demo] Full line fragment identifier (all of these words here should not be included in fragment)
const result = fetch(myURL)
.then(response => {
return response.json();
})
.then(myJson => {
console.log(JSON.stringify(myJson));
});
<!-- /// [demo] -->
result.then(console.log).catch(console.error);
</script>
`,
},
});

// Wait for the embedded fragment to be fetched and rendered into #main
expect(
await waitForText('#main', 'console.log(JSON.stringify(myJson));'),
).toBeTruthy();

const mainText = document.querySelector('#main').textContent;
expect(mainText).not.toContain('https://api.example.com/data');
expect(mainText).not.toContain('Full line fragment identifier');
expect(mainText).not.toContain('-->');
expect(mainText).not.toContain(
'result.then(console.log).catch(console.error);',
);
});

test('embed multiple file code fragments', async () => {
await docsifyInit({
markdown: {
Expand All @@ -186,7 +228,9 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
# Text between
[filename](_media/example3.js ':include :fragment=something_else_not_code')
[filename](_media/example4.js ':include :fragment=demo')
# Text after
`,
},
Expand All @@ -209,6 +253,12 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
example3 += 10;
/// [something_else_not_code]
console.log(example3);`,
'_media/example4.js': `
let example4 = 1;
### No fragment here
example4 += 10;
/// No fragment here
console.log(example4);`,
},
});

Expand All @@ -225,5 +275,8 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
expect(mainText).not.toContain('console.log(example1);');
expect(mainText).not.toContain('console.log(example2);');
expect(mainText).not.toContain('console.log(example3);');
expect(mainText).not.toContain('console.log(example4);');
expect(mainText).not.toContain('example4 += 10;');
expect(mainText).not.toContain('No fragment here');
});
});