feat: Fragment identifier full line ignore (#2626)

This commit is contained in:
Jake 2025-11-24 03:47:19 +01:00 committed by GitHub
parent f793e26215
commit e8117563ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 79 additions and 11 deletions

View File

@ -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:

View File

@ -135,6 +135,7 @@ export class Compiler {
}
embed.fragment = config.fragment;
embed.omitFragmentLine = config.omitFragmentLine;
return embed;
}

View File

@ -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) {

View File

@ -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();
}
@ -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(

View File

@ -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: {
@ -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
`,
},
@ -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);`,
},
});
@ -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 () => {