377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
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 = '<svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'
|
||
|
||
const DEFAULT_OPTIONS = {
|
||
mkit: {
|
||
// Enable HTML tags in source
|
||
html: true,
|
||
// Use '/' to close single tags (<br />).
|
||
// This is only for full CommonMark compatibility.
|
||
xhtmlOut: true,
|
||
// Convert '\n' in paragraphs into <br>
|
||
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 <pre... internal wrapper is skipped.
|
||
highlight: function (str, lang) {
|
||
if (lang && hljs.getLanguage(lang)) {
|
||
try {
|
||
return `<pre class="hljs"><code>${
|
||
hljs.highlight(lang, str, true).value
|
||
}</code></pre>`;
|
||
} catch (__) {}
|
||
}
|
||
|
||
return `<pre class="hljs"><code>${escape(str)}</code></pre>`;
|
||
},
|
||
},
|
||
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 (
|
||
<React.Fragment>
|
||
<Head>
|
||
<title>{(pageTitle || '').replace(/\$\{name\}/, name)}</title>
|
||
<link rel="shortcut icon" type="image/ico" href="/_static/favicon.ico" />
|
||
<link rel="stylesheet" href="/_static/page.css" />
|
||
<link rel="stylesheet" href="/_static/markdown.css" />
|
||
<link rel="stylesheet" href="/_static/highlight.css" />
|
||
<link rel="stylesheet" href="/_static/katex@0.15.3.css" />
|
||
<link rel="stylesheet" href="/_static/sequence-diagram-min.css" />
|
||
<script type="text/javascript" src="/_static/underscore-min.js"></script>
|
||
<script type="text/javascript" src="/_static/webfont.js"></script>
|
||
<script type="text/javascript" src="/_static/snap.svg.min.js"></script>
|
||
<script type="text/javascript" src="/_static/tweenlite.min.js"></script>
|
||
<script type="text/javascript" src="/_static/mermaid.min.js"></script>
|
||
<script type="text/javascript" src="/_static/sequence-diagram-min.js"></script>
|
||
<script type="text/javascript" src="/_static/katex@0.15.3.js"></script>
|
||
<script type="text/javascript" src="/_static/mhchem.min.js"></script>
|
||
<script type="text/javascript" src="/_static/raphael@2.3.0.min.js"></script>
|
||
<script type="text/javascript" src="/_static/flowchart@1.13.0.min.js"></script>
|
||
<script type="text/javascript" src="/_static/viz.js"></script>
|
||
<script type="text/javascript" src="/_static/full.render.js"></script>
|
||
</Head>
|
||
<main data-theme={this.state.theme}>
|
||
<div id="page-ctn" contentEditable={contentEditable ? 'true' : 'false'}>
|
||
{ disableFilename == 0 &&
|
||
<header
|
||
id="page-header"
|
||
onMouseEnter={this.showThemeButton}
|
||
onMouseLeave={this.hideThemeButton}
|
||
>
|
||
<h3>
|
||
<svg
|
||
viewBox="0 0 16 16"
|
||
version="1.1"
|
||
width="16"
|
||
height="16"
|
||
aria-hidden="true"
|
||
>
|
||
<path
|
||
fill-rule="evenodd"
|
||
d="M3 5h4v1H3V5zm0 3h4V7H3v1zm0 2h4V9H3v1zm11-5h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm2-6v9c0 .55-.45 1-1 1H9.5l-1 1-1-1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h5.5l1 1 1-1H15c.55 0 1 .45 1 1zm-8 .5L7.5 3H2v9h6V3.5zm7-.5H9.5l-.5.5V12h6V3z"
|
||
>
|
||
</path>
|
||
</svg>
|
||
{name}
|
||
</h3>
|
||
{themeModeIsVisible && (
|
||
<label id="toggle-theme" for="theme">
|
||
<input
|
||
id="theme"
|
||
type="checkbox"
|
||
checked={theme === "dark"}
|
||
onChange={this.handleThemeChange}
|
||
/>
|
||
<span>Dark Mode</span>
|
||
</label>
|
||
)}
|
||
</header>
|
||
}
|
||
<section
|
||
className="markdown-body"
|
||
dangerouslySetInnerHTML={{
|
||
__html: content
|
||
}}
|
||
/>
|
||
</div>
|
||
</main>
|
||
</React.Fragment>
|
||
)
|
||
}
|
||
}
|