Skip to content
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]`. By default, only identifiers are omitted. To omit the entire line containing the identifier in the fragment output, you can add the `:omitFragmentLine` option.

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.omitFragmentLine = config.omitFragmentLine;

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 containing fragment identifier (e.g. /// [demo])
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.omitFragmentLine,
);
}

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.omitFragmentLine,
);
}

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 :omitFragmentLine')
`,
},
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,6 +275,9 @@ 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');
});

test('embed file table cell', async () => {
Expand Down