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 (