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
26 changes: 20 additions & 6 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.
* @param {boolean} fullLine - The fragment identifier to search for.
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter description for fullLine is incorrect. It should describe that this is a boolean flag to enable full-line matching of fragment identifiers, not 'The fragment identifier to search for' (which is the description for the fragment parameter).

Suggested change
* @param {boolean} fullLine - The fragment identifier to search for.
* @param {boolean} fullLine - Boolean flag to enable full-line matching of fragment identifiers.

Copilot uses AI. Check for mistakes.
* @returns {string} - The extracted and demented content, or an empty string if not found.
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'demented' to 'dedented'.

Suggested change
* @returns {string} - The extracted and demented content, or an empty string if not found.
* @returns {string} - The extracted and dedented content, or an empty string if not found.

Copilot uses AI. Check for mistakes.
*/
function extractFragmentContent(text, fragment) {
function extractFragmentContent(text, fragment, fullLine) {
if (!fragment) {
return text;
}

let fragmentRegex = `###|\\/\\/\\/\\s*\\[${fragment}\\]`;
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern is incorrect when fullLine is false. This pattern matches either ### alone OR /// followed by \\s*[fragment], but it doesn't match ### followed by \\s*[fragment]. The pattern should be wrapped in a non-capturing group: let fragmentRegex = (?:###|\/\/\/)\s*\[${fragment}\]; to match either ### or ///, then \\s*[fragment].

Suggested change
let fragmentRegex = `###|\\/\\/\\/\\s*\\[${fragment}\\]`;
let fragmentRegex = `(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. However, this isn’t the final regex string. The non-capture group part is added just below this line

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
42 changes: 42 additions & 0 deletions 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 Down
Loading