diff --git a/package.json b/package.json index 11614ce0..b29037f8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,17 @@ }, "activationEvents": [], "contributes": { + "configuration": { + "type": "object", + "title": "Marp for VS Code", + "properties": { + "markdown.marp.enableHtml": { + "type": "boolean", + "default": false, + "description": "Enables all HTML elements in Marp Markdown." + } + } + }, "markdown.markdownItPlugins": true, "markdown.previewScripts": [ "./lib/preview.js" diff --git a/src/__mocks__/vscode.ts b/src/__mocks__/vscode.ts new file mode 100644 index 00000000..9f433711 --- /dev/null +++ b/src/__mocks__/vscode.ts @@ -0,0 +1,3 @@ +export const workspace = { + getConfiguration: jest.fn(() => new Map()), +} diff --git a/src/extension.test.ts b/src/extension.test.ts index af0df7ae..5ead046d 100644 --- a/src/extension.test.ts +++ b/src/extension.test.ts @@ -3,8 +3,23 @@ import markdownItKatex from '@neilsustc/markdown-it-katex' import { Marp } from '@marp-team/marp-core' import markdownIt from 'markdown-it' import markdownItEmoji from 'markdown-it-emoji' +import { workspace } from 'vscode' import { activate, extendMarkdownIt } from './extension' +jest.mock('vscode') + +const mockWorkspaceConfig = (conf: { [key: string]: any } = {}) => + jest.spyOn(workspace, 'getConfiguration').mockImplementation( + () => + new Map( + Object.entries({ + 'markdown.marp.enableHtml': false, + ...conf, + }) + ) + ) + +beforeEach(() => mockWorkspaceConfig()) afterEach(() => jest.restoreAllMocks()) describe('#activate', () => { @@ -108,4 +123,31 @@ describe('#extendMarkdownIt', () => { expect(html).toContain('') }) }) + + describe('Workspace config', () => { + const md = extendMarkdownIt(new markdownIt()) + + describe('markdown.marp.enableHtml', () => { + it('does not render HTML elements when disabled', () => { + mockWorkspaceConfig({ 'markdown.marp.enableHtml': false }) + + const html = md.render(marpMd('Hi')) + expect(html).not.toContain('Hi') + }) + + it("allows Marp Core's whitelisted HTML elements when disabled", () => { + mockWorkspaceConfig({ 'markdown.marp.enableHtml': false }) + + const html = md.render(marpMd('line
break')) + expect(html).toContain('line
break') + }) + + it('renders HTML elements when enabled', () => { + mockWorkspaceConfig({ 'markdown.marp.enableHtml': true }) + + const html = md.render(marpMd('Hi')) + expect(html).toContain('Hi') + }) + }) + }) }) diff --git a/src/extension.ts b/src/extension.ts index d1f6fb64..50dbc849 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,4 +1,5 @@ import { Marp } from '@marp-team/marp-core' +import { workspace } from 'vscode' const frontMatterRegex = /^---\s*([^]*)?(?:-{3}|\.{3})\s*/ const marpDirectiveRegex = /^marp\s*:\s*true\s*$/m @@ -11,8 +12,12 @@ export function extendMarkdownIt(md: any) { md.use(marp.markdownItPlugins) .use(instance => { + let originalOptions + // Detect `marp: true` front-matter option instance.core.ruler.before('normalize', 'marp_vscode_toggle', state => { + originalOptions = instance.options + if (state.inlineMode) return const fmMatch = frontMatterRegex.exec(state.src) @@ -23,15 +28,29 @@ export function extendMarkdownIt(md: any) { instance[marpVscodeEnabled] = enabled state.marpit(enabled) - // Avoid collision to the other math plugins (markdown-it-katex) if (enabled) { + // Avoid collision to the other math plugins (markdown-it-katex) md.block.ruler.disable('math_block', true) md.inline.ruler.disable('math_inline', true) + + // Override HTML option + instance.set({ + html: workspace + .getConfiguration() + .get('markdown.marp.enableHtml') + ? true + : marp.options.html, + }) } else { md.block.ruler.enable('math_block', true) md.inline.ruler.enable('math_inline', true) } }) + + instance.core.ruler.push('marp_vscode_restore_options', state => { + if (state.inlineMode) return + instance.set(originalOptions) + }) }) .use(instance => { let originalEmojiRule