From b64a355d496a6380f61bd76473f7b1f691bbafe5 Mon Sep 17 00:00:00 2001 From: Yiyi Wang Date: Sun, 10 Dec 2023 18:03:14 +0800 Subject: [PATCH] 0.9.7 (#348) * fix: Removed one github-dark background css attribute * fix: Disabled code block data-source-line insertion * fix: Added .emf image so not embedded as binary * Revert "fix: Disabled code block data-source-line insertion" This reverts commit 4fb10bb672e9af0ec602f0b58414877c1c1bbdf5. * feat: Added enablePreviewZenMode option * feat: Updated the right click context menu * fix: Fixed rendering vega-lite in Reveal.js slide * doc: Updated the CHANGELOG.md * refactor: Improved the performance a bit while enabled zen mode * doc: Updated CHANGELOG.md --- CHANGELOG.md | 13 + README.md | 4 + package.json | 2 +- src/markdown-engine/index.ts | 3 + src/markdown-engine/transformer.ts | 2 +- src/notebook/types.ts | 7 + src/webview/components/ContextMenu.tsx | 163 +++++----- src/webview/components/Footer.tsx | 8 +- src/webview/components/Preview.tsx | 7 +- src/webview/containers/preview.ts | 398 +++++++++++++------------ styles/preview.less | 6 + styles/prism_theme/github-dark.less | 1 - 12 files changed, 330 insertions(+), 284 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b5bf0e5..da14a2cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ Please visit https://github.com/shd101wyy/vscode-markdown-preview-enhanced/relea ## [Unreleased] +## [0.9.7] - 2023-12-10 + +### New features + +- Added `enablePreviewZenMode` option and reorganized the right-click context menu. + + ![image](https://github.com/shd101wyy/crossnote/assets/1908863/26e2237e-c6e2-433e-a063-6de2c01a64bb) + +### Bug fixes + +- Fixed rendering `vega-lite` in `Reveal.js` slide: https://github.com/shd101wyy/vscode-markdown-preview-enhanced/issues/1880 +- Removed one github-dark background css attribute: https://github.com/shd101wyy/crossnote/issues/344 + ## [0.9.6] - 2023-10-24 ### Changes diff --git a/README.md b/README.md index e3ad031b..e079ef49 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ const config = { // In Markdown, a single newline character doesn't cause a line break in the generated HTML. In GitHub Flavored Markdown, that is not true. Enable this config option to insert line breaks in rendered HTML for single newlines in Markdown source. breakOnSingleNewLine: true, + // Whether to enable preview zen mode. + // Enable this option will hide unnecessary UI elements in preview unless your mouse is over it. + enablePreviewZenMode: boolean; + // Enable smartypants and other sweet transforms. enableTypographer: false, diff --git a/package.json b/package.json index ea8da0bb..b320c831 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crossnote", - "version": "0.9.6", + "version": "0.9.7", "description": "A powerful markdown notebook tool", "keywords": [ "markdown" diff --git a/src/markdown-engine/index.ts b/src/markdown-engine/index.ts index 070bd03e..9486ef03 100644 --- a/src/markdown-engine/index.ts +++ b/src/markdown-engine/index.ts @@ -360,6 +360,9 @@ if (typeof(window['Reveal']) !== 'undefined') { } for (var i = 0; i < vegaEls.length; i++) { const vegaEl = vegaEls[i] + if (vegaEl.hasAttribute("data-processed")) { + continue; + } try { var spec = JSON.parse(vegaEl.textContent); vegaEmbed(vegaEl, spec, { actions: false, renderer: 'svg' }) diff --git a/src/markdown-engine/transformer.ts b/src/markdown-engine/transformer.ts index 6ae015d5..da672854 100644 --- a/src/markdown-engine/transformer.ts +++ b/src/markdown-engine/transformer.ts @@ -693,7 +693,7 @@ export async function transformMarkdown( } // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types else if ( - extname.match(/^\.(apng|avif|gif|jpeg|jpg|png|svg|bmp|webp)/) || + extname.match(/^\.(apng|avif|gif|jpeg|jpg|png|svg|bmp|webp|emf)/) || extname === '' // NOTE: For example, for github image like: ![Screenshot from 2023-10-15 15-34-27](https://github.com/shd101wyy/crossnote/assets/1908863/ede91390-3cca-4b83-8e30-33027bf0a363) ) { if (importMatch || wikilinkImportMatch) { diff --git a/src/notebook/types.ts b/src/notebook/types.ts index 247ec131..2eb0aa9e 100644 --- a/src/notebook/types.ts +++ b/src/notebook/types.ts @@ -193,6 +193,12 @@ export interface NotebookConfig { */ parserConfig: ParserConfig; + /** + * Whether to enable preview zen mode. + * Enable this option will hide unnecessary UI elements in preview unless your mouse is over it. + */ + enablePreviewZenMode: boolean; + /** * Whether to break on single new line. * @@ -534,6 +540,7 @@ export function getDefaultNotebookConfig(): NotebookConfig { mathjaxConfig: getDefaultMathjaxConfig(), katexConfig: getDefaultKatexConfig(), parserConfig: getDefaultParserConfig(), + enablePreviewZenMode: false, usePandocParser: false, breakOnSingleNewLine: true, enableTypographer: false, diff --git a/src/webview/components/ContextMenu.tsx b/src/webview/components/ContextMenu.tsx index 2b865bf6..b31b30aa 100644 --- a/src/webview/components/ContextMenu.tsx +++ b/src/webview/components/ContextMenu.tsx @@ -1,6 +1,5 @@ import { mdiCancel, - mdiExport, mdiExportVariant, mdiImageOutline, mdiInformationOutline, @@ -9,9 +8,11 @@ import { mdiOpenInNew, mdiPaletteOutline, mdiPencil, + mdiSpaOutline, mdiSync, } from '@mdi/js'; import Icon from '@mdi/react'; +import classNames from 'classnames'; import React, { useCallback } from 'react'; import { Item, ItemParams, Menu, Separator, Submenu } from 'react-contexify'; import 'react-contexify/ReactContexify.css'; @@ -32,6 +33,7 @@ export default function ContextMenu() { sourceUri, theme, isPresentationMode, + enablePreviewZenMode, } = PreviewContainer.useContainer(); const handleItemClick = useCallback( @@ -89,6 +91,10 @@ export default function ContextMenu() { postMessage('markdownExport', [sourceUri.current]); break; } + case 'toggle-zen-mode': { + postMessage('togglePreviewZenMode', [sourceUri.current]); + break; + } case 'open-image-helper': { setShowImageHelper(true); break; @@ -215,105 +221,72 @@ export default function ContextMenu() { size={0.8} className="mr-2" > - HTML + Export } > - - {'HTML (offline)'} - - - {'HTML (cdn hosted)'} - - - )} - {!isVSCodeWebExtension && ( - HTML + } + > + + {'HTML (offline)'} + + + {'HTML (cdn hosted)'} + + + + Chrome (Puppeteer) + + } + > + + PDF + + + PNG + + + JPEG + + + - - Chrome (Puppeteer) + PDF (Prince) - } - > - - PDF - - PNG - - - JPEG + eBook + } + > + + ePub + + + Mobi + + + PDF + + + HTML + + + + Pandoc - - )} - {!isVSCodeWebExtension && ( - - - - PDF (Prince) - - - )} - {!isVSCodeWebExtension && ( - - - eBook + Save as Markdown - } - > - - ePub - - - Mobi - - - PDF - - - HTML )} - {!isVSCodeWebExtension && ( - - - - Pandoc - - - )} - {!isVSCodeWebExtension && ( - - - - Save as Markdown - - - )} {!isVSCodeWebExtension && } )} + + + + + Zen Mode + + + {!isVSCodeWebExtension && ( <> - diff --git a/src/webview/components/Footer.tsx b/src/webview/components/Footer.tsx index 0f3dd14e..67b87d65 100644 --- a/src/webview/components/Footer.tsx +++ b/src/webview/components/Footer.tsx @@ -15,6 +15,7 @@ export default function Footer() { showBacklinks, theme, markdown, + enablePreviewZenMode, } = PreviewContainer.useContainer(); const [readingTimeEstimation, setReadingTimeEstimation] = useState< | { @@ -39,12 +40,15 @@ export default function Footer() { data-theme={theme} >
- {readingTimeEstimation && ( + {!enablePreviewZenMode && readingTimeEstimation && (
{readingTimeEstimation.text}
diff --git a/src/webview/components/Preview.tsx b/src/webview/components/Preview.tsx index eafa79a8..0a20db3b 100644 --- a/src/webview/components/Preview.tsx +++ b/src/webview/components/Preview.tsx @@ -14,6 +14,7 @@ import { Topbar } from './Topbar'; export default function Preview() { const { + enablePreviewZenMode, hiddenPreviewElement, isPresentationMode, isLoadingPreview, @@ -80,9 +81,11 @@ export default function Preview() { {/** Context menu */} {/** Floating Actions */} - + {!enablePreviewZenMode && } {/** Markdown Editor */} - {highlightElementBeingEdited && } + {!enablePreviewZenMode && highlightElementBeingEdited && ( + + )}
); } diff --git a/src/webview/containers/preview.ts b/src/webview/containers/preview.ts index 25b0fa87..d162ae82 100644 --- a/src/webview/containers/preview.ts +++ b/src/webview/containers/preview.ts @@ -163,6 +163,9 @@ const PreviewContainer = createContainer(() => { const isVSCode = useMemo(() => { return !!config.isVSCode; }, [config]); + const enablePreviewZenMode = useMemo(() => { + return !!config.enablePreviewZenMode; + }, [config]); const contextMenuId = useMemo(() => { return 'crossnote-context-menu'; }, []); @@ -862,120 +865,128 @@ const PreviewContainer = createContainer(() => { } }, [postMessage]); - const bindHighlightEvent = useCallback((previewElement: HTMLDivElement) => { - setHighlightElement(null); - const sourceLineElements = - previewElement.querySelectorAll('[data-source-line]'); - - const highlightElementsThatAddedEventSet = new Set(); - const sourceLineElementToContainerElementMap = new Map< - Element | HTMLElement, - Element | HTMLElement - >(); - - highlightElementToLinesMap.current = new Map< - HTMLElement | Element, - number[] - >(); - highlightElementLines.current = []; - const startLinesSet = new Set(); - - const bindHighlightElementsEvent = ( - highlightElements: (HTMLElement | Element)[], - startLine: number, - ) => { - startLinesSet.add(startLine); - - if (highlightElements.length === 0) { + const bindHighlightEvent = useCallback( + (previewElement: HTMLDivElement) => { + // NOTE: No need to handle this event in zen mode. + if (enablePreviewZenMode) { return; } - // console.log('* highlightElements: ', highlightElements); - const firstHighlightElement = highlightElements[0] as HTMLElement; - const linesSet = new Set([startLine]); - - // Iterate over highlightElementToLinesMap - // If firstHighlightElement contains the highlightElement in the map - // Add its lines to the current linesSet - for (const [ - highlightElement, - lines, - ] of highlightElementToLinesMap.current) { - if (firstHighlightElement.contains(highlightElement)) { - lines.forEach((line) => { - linesSet.add(line); - }); - } - } - // Add event listeners - highlightElements.forEach((highlightElement) => { - if (highlightElementsThatAddedEventSet.has(highlightElement)) { - const lines = - highlightElementToLinesMap.current.get(highlightElement); - if (lines) { + setHighlightElement(null); + const sourceLineElements = + previewElement.querySelectorAll('[data-source-line]'); + + const highlightElementsThatAddedEventSet = new Set< + Element | HTMLElement + >(); + const sourceLineElementToContainerElementMap = new Map< + Element | HTMLElement, + Element | HTMLElement + >(); + + highlightElementToLinesMap.current = new Map< + HTMLElement | Element, + number[] + >(); + highlightElementLines.current = []; + const startLinesSet = new Set(); + + const bindHighlightElementsEvent = ( + highlightElements: (HTMLElement | Element)[], + startLine: number, + ) => { + startLinesSet.add(startLine); + + if (highlightElements.length === 0) { + return; + } + // console.log('* highlightElements: ', highlightElements); + const firstHighlightElement = highlightElements[0] as HTMLElement; + const linesSet = new Set([startLine]); + + // Iterate over highlightElementToLinesMap + // If firstHighlightElement contains the highlightElement in the map + // Add its lines to the current linesSet + for (const [ + highlightElement, + lines, + ] of highlightElementToLinesMap.current) { + if (firstHighlightElement.contains(highlightElement)) { lines.forEach((line) => { linesSet.add(line); }); } - return; - } else { - highlightElementsThatAddedEventSet.add(highlightElement); } - highlightElement.addEventListener( - /*'mouseenter'*/ 'mouseover', - (event) => { - event.stopPropagation(); - setIsMouseOverPreview(true); - - // Remove "highlight-line" class name from all highlight elements. - const currentHighlightElements = Array.from( - document.getElementsByClassName('highlight-line'), - ); - currentHighlightElements.forEach((currentHighlightElement) => { - currentHighlightElement.classList.remove('highlight-line'); - }); - // Add "highlight-line" class name. - highlightElements.forEach((highlightElement) => { - highlightElement.classList.add('highlight-line'); - }); - setHighlightElement(firstHighlightElement); - }, - ); - highlightElement.addEventListener( - /*'mouseleave'*/ 'mouseout', - (event) => { - event.stopPropagation(); - highlightElements.forEach((highlightElement) => { - highlightElement.classList.remove('highlight-line'); - highlightElement.classList.remove('highlight-active'); - }); - setHighlightElement(null); - }, + // Add event listeners + highlightElements.forEach((highlightElement) => { + if (highlightElementsThatAddedEventSet.has(highlightElement)) { + const lines = + highlightElementToLinesMap.current.get(highlightElement); + if (lines) { + lines.forEach((line) => { + linesSet.add(line); + }); + } + return; + } else { + highlightElementsThatAddedEventSet.add(highlightElement); + } + highlightElement.addEventListener( + /*'mouseenter'*/ 'mouseover', + (event) => { + event.stopPropagation(); + setIsMouseOverPreview(true); + + // Remove "highlight-line" class name from all highlight elements. + const currentHighlightElements = Array.from( + document.getElementsByClassName('highlight-line'), + ); + currentHighlightElements.forEach((currentHighlightElement) => { + currentHighlightElement.classList.remove('highlight-line'); + }); + + // Add "highlight-line" class name. + highlightElements.forEach((highlightElement) => { + highlightElement.classList.add('highlight-line'); + }); + setHighlightElement(firstHighlightElement); + }, + ); + highlightElement.addEventListener( + /*'mouseleave'*/ 'mouseout', + (event) => { + event.stopPropagation(); + highlightElements.forEach((highlightElement) => { + highlightElement.classList.remove('highlight-line'); + highlightElement.classList.remove('highlight-active'); + }); + setHighlightElement(null); + }, + ); + }); + highlightElementToLinesMap.current.set( + firstHighlightElement, + Array.from(linesSet).sort((a, b) => a - b), ); - }); - highlightElementToLinesMap.current.set( - firstHighlightElement, - Array.from(linesSet).sort((a, b) => a - b), - ); - }; + }; - for (let i = sourceLineElements.length - 1; i >= 0; i--) { - const sourceLineElement = sourceLineElements[i]; - const dataSourceLine = getDataSourceLine(sourceLineElement) ?? 0; + for (let i = sourceLineElements.length - 1; i >= 0; i--) { + const sourceLineElement = sourceLineElements[i]; + const dataSourceLine = getDataSourceLine(sourceLineElement) ?? 0; - if (dataSourceLine > totalLineCount.current) { - // FIXME: This means we didn't get the source map correctly. - return; - } + if (dataSourceLine > totalLineCount.current) { + // FIXME: This means we didn't get the source map correctly. + return; + } - // Ignore the link - if (sourceLineElement.tagName === 'A') { - continue; - } + // Ignore the link + if (sourceLineElement.tagName === 'A') { + continue; + } - // Input List item - /* + // Input List item + /* if ( sourceLineElement.tagName === 'INPUT' && sourceLineElement.classList.contains('task-list-item-checkbox') @@ -997,112 +1008,119 @@ const PreviewContainer = createContainer(() => { } } */ - // Code chunk - if ( - sourceLineElement.tagName === 'PRE' && - sourceLineElement.parentElement?.classList.contains('input-div') && - sourceLineElement.parentElement.parentElement?.classList.contains( - 'code-chunk', - ) - ) { - const highlightElement = sourceLineElement.parentElement.parentElement; - if (highlightElement) { - bindHighlightElementsEvent([highlightElement], dataSourceLine); + // Code chunk + if ( + sourceLineElement.tagName === 'PRE' && + sourceLineElement.parentElement?.classList.contains('input-div') && + sourceLineElement.parentElement.parentElement?.classList.contains( + 'code-chunk', + ) + ) { + const highlightElement = + sourceLineElement.parentElement.parentElement; + if (highlightElement) { + bindHighlightElementsEvent([highlightElement], dataSourceLine); - sourceLineElementToContainerElementMap.set( - sourceLineElement, - highlightElement, - ); + sourceLineElementToContainerElementMap.set( + sourceLineElement, + highlightElement, + ); + } } - } - // Image and link - else if ( - sourceLineElement.tagName === 'IMG' || - sourceLineElement.tagName === 'A' - ) { - const highlightElement = sourceLineElement.parentElement; - if (highlightElement) { - bindHighlightElementsEvent([highlightElement], dataSourceLine); + // Image and link + else if ( + sourceLineElement.tagName === 'IMG' || + sourceLineElement.tagName === 'A' + ) { + const highlightElement = sourceLineElement.parentElement; + if (highlightElement) { + bindHighlightElementsEvent([highlightElement], dataSourceLine); + + sourceLineElementToContainerElementMap.set( + sourceLineElement, + highlightElement, + ); + } + } + // Other elements + else { + bindHighlightElementsEvent([sourceLineElement], dataSourceLine); sourceLineElementToContainerElementMap.set( sourceLineElement, - highlightElement, + sourceLineElement.parentElement?.tagName === 'P' + ? sourceLineElement.parentElement + : sourceLineElement, ); } - } - // Other elements - else { - bindHighlightElementsEvent([sourceLineElement], dataSourceLine); - - sourceLineElementToContainerElementMap.set( - sourceLineElement, - sourceLineElement.parentElement?.tagName === 'P' - ? sourceLineElement.parentElement - : sourceLineElement, - ); - } - // First [data-source-line] element - // Bind all elements above it - if (i == 0) { - const highlightElements: (Element | HTMLElement)[] = []; - let siblingElement = - sourceLineElementToContainerElementMap.get(sourceLineElement) - ?.previousElementSibling; - while (siblingElement) { - if ( - !(siblingElement.tagName === 'P' && siblingElement.innerHTML === '') - ) { - highlightElements.push(siblingElement); + // First [data-source-line] element + // Bind all elements above it + if (i == 0) { + const highlightElements: (Element | HTMLElement)[] = []; + let siblingElement = + sourceLineElementToContainerElementMap.get(sourceLineElement) + ?.previousElementSibling; + while (siblingElement) { + if ( + !( + siblingElement.tagName === 'P' && + siblingElement.innerHTML === '' + ) + ) { + highlightElements.push(siblingElement); + } + siblingElement = siblingElement.previousElementSibling; } - siblingElement = siblingElement.previousElementSibling; + bindHighlightElementsEvent(highlightElements, 0); } - bindHighlightElementsEvent(highlightElements, 0); - } - // Check elements between this and the next [data-source-line] element who has the same parent - if (i < sourceLineElements.length - 1) { - for (let j = i + 1; j < sourceLineElements.length; j++) { - const nextSourceLineElement = sourceLineElements[j]; - const sourceLineElementContainer = - sourceLineElementToContainerElementMap.get(sourceLineElement); - const nextSourceLineElementContainer = - sourceLineElementToContainerElementMap.get(nextSourceLineElement); - if ( - sourceLineElementContainer && - nextSourceLineElementContainer && - sourceLineElementContainer !== nextSourceLineElementContainer && - sourceLineElementContainer.parentElement === - nextSourceLineElementContainer.parentElement - ) { - const highlightElements: (Element | HTMLElement)[] = []; - let siblingElement = sourceLineElementContainer.nextElementSibling; - while ( - siblingElement && - siblingElement !== nextSourceLineElementContainer + // Check elements between this and the next [data-source-line] element who has the same parent + if (i < sourceLineElements.length - 1) { + for (let j = i + 1; j < sourceLineElements.length; j++) { + const nextSourceLineElement = sourceLineElements[j]; + const sourceLineElementContainer = + sourceLineElementToContainerElementMap.get(sourceLineElement); + const nextSourceLineElementContainer = + sourceLineElementToContainerElementMap.get(nextSourceLineElement); + if ( + sourceLineElementContainer && + nextSourceLineElementContainer && + sourceLineElementContainer !== nextSourceLineElementContainer && + sourceLineElementContainer.parentElement === + nextSourceLineElementContainer.parentElement ) { - if ( - !( - siblingElement.tagName === 'P' && - siblingElement.innerHTML === '' - ) + const highlightElements: (Element | HTMLElement)[] = []; + let siblingElement = + sourceLineElementContainer.nextElementSibling; + while ( + siblingElement && + siblingElement !== nextSourceLineElementContainer ) { - highlightElements.push(siblingElement); + if ( + !( + siblingElement.tagName === 'P' && + siblingElement.innerHTML === '' + ) + ) { + highlightElements.push(siblingElement); + } + siblingElement = siblingElement.nextElementSibling; } - siblingElement = siblingElement.nextElementSibling; - } - bindHighlightElementsEvent(highlightElements, dataSourceLine); - break; + bindHighlightElementsEvent(highlightElements, dataSourceLine); + break; + } } } } - } - highlightElementLines.current = Array.from(startLinesSet).sort( - (a, b) => a - b, - ); - }, []); + highlightElementLines.current = Array.from(startLinesSet).sort( + (a, b) => a - b, + ); + }, + [enablePreviewZenMode], + ); const updateHtml = useCallback( (html: string, id: string, classes: string) => { @@ -1137,7 +1155,9 @@ const PreviewContainer = createContainer(() => { previewElement.current.id = id || ''; previewElement.current.setAttribute( 'class', - `crossnote markdown-preview ${classes}`, + `crossnote markdown-preview ${ + enablePreviewZenMode ? 'zen-mode' : '' + } ${classes}`, ); // scroll to initial position @@ -1166,6 +1186,7 @@ const PreviewContainer = createContainer(() => { isPresentationMode, postMessage, scrollToRevealSourceLine, + enablePreviewZenMode, ], ); @@ -1640,6 +1661,7 @@ const PreviewContainer = createContainer(() => { clickSidebarTocButton, config, contextMenuId, + enablePreviewZenMode, getHighlightElementLineRange, hiddenPreviewElement, highlightElement, diff --git a/styles/preview.less b/styles/preview.less index 92f3f735..87d724e0 100644 --- a/styles/preview.less +++ b/styles/preview.less @@ -221,6 +221,12 @@ body:not([data-presentation-mode]) { } } + .crossnote.zen-mode { + .final-line.end-of-document { + display: none; + } + } + &[data-preview-theme='dark'] { .final-line.end-of-document { color: #555; diff --git a/styles/prism_theme/github-dark.less b/styles/prism_theme/github-dark.less index 1ab2b51f..3dbdb619 100644 --- a/styles/prism_theme/github-dark.less +++ b/styles/prism_theme/github-dark.less @@ -104,7 +104,6 @@ pre[class*='language-'] > code[class*='language-'] { .language-css .token.string, .style .token.string { color: #a5d6ff; - background: #161b22; } .token.atrule, .token.attr-value,