import React from 'react' import Head from 'next/head' import io from 'socket.io-client' import MarkdownIt from 'markdown-it' import hljs from 'highlight.js' import emoji from 'markdown-it-emoji' import taskLists from 'markdown-it-task-lists' import footnote from 'markdown-it-footnote' import markdownItAnchor from 'markdown-it-anchor' import markdownItToc from 'markdown-it-toc-done-right' import markdownDeflist from 'markdown-it-deflist' import mk from './katex' import chart from './chart' import mkitMermaid from './mermaid' import linenumbers from './linenumbers' import image from './image' import diagram, { renderDiagram } from './diagram' import flowchart, { renderFlowchart } from './flowchart' import dot, { renderDot } from './dot' import blockUml from './blockPlantuml' import codeUml from './plantuml' import scrollToLine from './scroll' import { meta } from './meta'; import markdownImSize from './markdown-it-imsize' import { escape} from './utils'; const anchorSymbol = '' const DEFAULT_OPTIONS = { mkit: { // Enable HTML tags in source html: true, // Use '/' to close single tags (
). // This is only for full CommonMark compatibility. xhtmlOut: true, // Convert '\n' in paragraphs into
breaks: false, // CSS language prefix for fenced blocks. Can be // useful for external highlighters. langPrefix: 'language-', // Autoconvert URL-like text to links linkify: true, // Enable some language-neutral replacement + quotes beautification typographer: true, // Double + single quotes replacement pairs, when typographer enabled, // and smartquotes on. Could be either a String or an Array. // // For example, you can use '«»„“' for Russian, '„“‚‘' for German, // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). quotes: '“”‘’', // Highlighter function. Should return escaped HTML, // or '' if the source string is not changed and should be escaped externally. // If result starts with ${ hljs.highlight(lang, str, true).value }`; } catch (__) {} } return `
${escape(str)}
`; }, }, katex: { 'throwOnError': false, 'errorColor': ' #cc0000' }, uml: {}, toc: { listType: 'ul' } } export default class PreviewPage extends React.Component { constructor(props) { super(props) this.preContent = '' this.timer = undefined this.state = { name: '', cursor: '', content: '', pageTitle: '', theme: '', themeModeIsVisible: false, contentEditable: false, disableFilename: 1 } this.showThemeButton = this.showThemeButton.bind(this) this.hideThemeButton = this.hideThemeButton.bind(this) this.handleThemeChange = this.handleThemeChange.bind(this) } handleThemeChange() { this.setState((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light', })) } showThemeButton() { this.setState({ themeModeIsVisible: true }) } hideThemeButton() { this.setState({ themeModeIsVisible: false }) } componentDidMount() { const socket = io({ query: { bufnr: window.location.pathname.split('/')[2] } }) window.socket = socket socket.on('connect', this.onConnect.bind(this)) socket.on('disconnect', this.onDisconnect.bind(this)) socket.on('close', this.onClose.bind(this)) socket.on('refresh_content', this.onRefreshContent.bind(this)) socket.on('close_page', this.onClose.bind(this)) } onConnect() { console.log('connect success') } onDisconnect() { console.log('disconnect') } onClose() { console.log('close') window.close() } onRefreshContent({ options = {}, isActive, winline, winheight, cursor, pageTitle = '', theme, name = '', content }) { if (!this.md) { const { mkit = {}, katex = {}, uml = {}, hide_yaml_meta: hideYamlMeta = 1, sequence_diagrams: sequenceDiagrams = {}, flowchart_diagrams: flowchartDiagrams = {}, toc = {} } = options // markdown-it this.md = new MarkdownIt({ ...DEFAULT_OPTIONS.mkit, ...mkit }) if (hideYamlMeta === 1) { this.md.use(meta([['---', '\\.\\.\\.'], ['---', '\\.\\.\\.']])) } // katex this.md .use(mk, { ...DEFAULT_OPTIONS.katex, ...katex }) .use(blockUml, { ...DEFAULT_OPTIONS.uml, ...uml }) .use(codeUml, { ...DEFAULT_OPTIONS.uml, ...uml }) .use(emoji) .use(taskLists) .use(markdownDeflist) .use(footnote) .use(image) .use(markdownImSize) .use(linenumbers) .use(mkitMermaid) .use(chart.chartPlugin) .use(diagram, { ...sequenceDiagrams }) .use(flowchart, flowchartDiagrams) .use(dot) .use(markdownItAnchor, { permalink: true, permalinkBefore: true, permalinkSymbol: anchorSymbol, permalinkClass: 'anchor' }) .use(markdownItToc, { ...DEFAULT_OPTIONS.toc, ...toc }) } // Theme already applied if (this.state.theme) { theme = this.state.theme } // Define the theme according to the preferences of the system else if (!theme || !['light', 'dark'].includes(theme)) { if ( window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ) { theme = 'dark' } } const newContent = content.join('\n') const refreshContent = this.preContent !== newContent this.preContent = newContent const refreshScroll = () => { if (isActive && !options.disable_sync_scroll) { scrollToLine[options.sync_scroll_type || 'middle']({ cursor: cursor[1], winline, winheight, len: content.length }) } } const refreshRender = () => { this.setState({ cursor, name: ((name) => { let tokens = name.split(/\\|\//).pop().split('.'); return tokens.length > 1 ? tokens.slice(0, -1).join('.') : tokens[0]; })(name), ...( refreshContent ? { content: this.md.render(newContent) } : {} ), pageTitle, theme, contentEditable: options.content_editable, disableFilename: options.disable_filename }, () => { if (refreshContent) { try { // eslint-disable-next-line mermaid.initialize(options.maid || {}) // eslint-disable-next-line mermaid.init(undefined, document.querySelectorAll('.mermaid')) } catch (e) { } chart.render() renderDiagram() renderFlowchart() renderDot() } refreshScroll() }) } if (!this.preContent) { refreshRender() } else { if (!refreshContent) { refreshScroll() } else { if (this.timer) { clearTimeout(this.timer) } this.timer = setTimeout(() => { refreshRender() }, 16); } } } render() { const { theme, content, name, pageTitle, themeModeIsVisible, contentEditable, disableFilename, } = this.state return ( {(pageTitle || '').replace(/\$\{name\}/, name)}
{ disableFilename == 0 && }
) } }