1
0
mirror of https://github.com/amix/vimrc synced 2025-07-10 03:25:00 +08:00

Add markdown preview, update rust.vim

This commit is contained in:
Kurtis Moxley
2022-05-19 20:12:11 +08:00
parent d26bc75459
commit d6ef288a88
156 changed files with 29900 additions and 0 deletions

View File

@ -0,0 +1,5 @@
// 404 page
export default () => (
<div>404</div>
)

View File

@ -0,0 +1,137 @@
// Process block-level uml diagrams
const plantumlEncoder = require("plantuml-encoder");
module.exports = function umlPlugin(md, options) {
function generateSourceDefault(umlCode, pluginOptions) {
var imageFormat = pluginOptions.imageFormat || 'img';
var diagramName = pluginOptions.diagramName || 'uml';
var server = pluginOptions.server || 'https://www.plantuml.com/plantuml';
var zippedCode = plantumlEncoder.encode(umlCode);
return server + '/' + imageFormat + '/' + zippedCode;
}
options = options || {};
var openMarker = options.openMarker || '@startuml',
openChar = openMarker.charCodeAt(0),
closeMarker = options.closeMarker || '@enduml',
closeChar = closeMarker.charCodeAt(0),
render = options.render || md.renderer.rules.image,
generateSource = options.generateSource || generateSourceDefault;
function uml(state, startLine, endLine, silent) {
var nextLine, markup, params, token, i,
autoClosed = false,
start = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
// Check out the first character quickly,
// this should filter out most of non-uml blocks
//
if (openChar !== state.src.charCodeAt(start)) { return false; }
// Check out the rest of the marker string
//
for (i = 0; i < openMarker.length; ++i) {
if (openMarker[i] !== state.src[start + i]) { return false; }
}
markup = state.src.slice(start, start + i);
params = state.src.slice(start + i, max);
// Since start is found, we can report success here in validation mode
//
if (silent) { return true; }
// Search for the end of the block
//
nextLine = startLine;
for (;;) {
nextLine++;
if (nextLine >= endLine) {
// unclosed block should be autoclosed by end of document.
// also block seems to be autoclosed by end of parent
break;
}
start = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (start < max && state.sCount[nextLine] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
// - ```
// test
break;
}
if (closeChar !== state.src.charCodeAt(start)) {
// didn't find the closing fence
continue;
}
if (state.sCount[nextLine] > state.sCount[startLine]) {
// closing fence should not be indented with respect of opening fence
continue;
}
var closeMarkerMatched = true;
for (i = 0; i < closeMarker.length; ++i) {
if (closeMarker[i] !== state.src[start + i]) {
closeMarkerMatched = false;
break;
}
}
if (!closeMarkerMatched) {
continue;
}
// make sure tail has spaces only
if (state.skipSpaces(start + i) < max) {
continue;
}
// found!
autoClosed = true;
break;
}
var contents = state.src
.split('\n')
.slice(startLine + 1, nextLine)
.join('\n');
// We generate a token list for the alt property, to mimic what the image parser does.
var altToken = [];
// Remove leading space if any.
var alt = params ? params.slice(1) : 'uml diagram';
state.md.inline.parse(
alt,
state.md,
state.env,
altToken
);
token = state.push('uml_diagram', 'img', 0);
// alt is constructed from children. No point in populating it here.
token.attrs = [ [ 'src', generateSource(contents, options) ], [ 'alt', '' ] ];
token.block = true;
token.children = altToken;
token.info = params;
token.map = [ startLine, nextLine ];
token.markup = markup;
state.line = nextLine + (autoClosed ? 1 : 0);
return true;
}
md.block.ruler.before('fence', 'uml_diagram', uml, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
});
md.renderer.rules.uml_diagram = render;
};

View File

@ -0,0 +1,34 @@
import Chart from 'chart.js'
function render () {
document.querySelectorAll('.chartjs').forEach(element => {
try {
// eslint-disable-next-line no-new
new Chart(element, JSON.parse(element.textContent))
} catch (e) {
element.outerHTML = `<pre>Chart.js complains: "${e}"</pre>`
}
})
}
const chartPlugin = (md) => {
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
if (token.info && token.info.trim() === 'chart') {
const code = token.content.trim()
try {
const json = JSON.parse(code)
return `<canvas class="chartjs">${JSON.stringify(json)}</canvas>`
} catch (e) { // JSON.parse exception
return `<pre>${e}</pre>`
}
}
return temp(tokens, idx, options, env, slf)
}
}
export default {
render,
chartPlugin
}

View File

@ -0,0 +1,42 @@
let options = {}
const diagram = (md, opts = {}) => {
options = opts
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
try {
if (token.info && token.info.trim() === 'sequence-diagrams') {
const code = token.content.trim()
return `<div class="sequence-diagrams">${code}</div>`
}
} catch (e) {
console.error(`Parse Diagram Error: `, e)
}
return temp(tokens, idx, options, env, slf)
}
}
export const renderDiagram = () => {
let list = document.querySelectorAll('.sequence-diagrams')
if (!list) {
return
}
list.forEach(item => {
try {
let d = window.Diagram.parse(item.textContent)
item.className = ''
item.textContent = ''
d.drawSVG(item, {
theme: 'hand',
...options
})
d = null
} catch (e) {
console.error(`Parse Sequence-diagrams Error: ${e}`)
}
})
list = null
}
export default diagram

View File

@ -0,0 +1,39 @@
let options = {}
const dot = (md, opts = {}) => {
options = opts
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
try {
if (token.info && (token.info.trim() === 'dot' || token.info.trim() === 'graphviz')) {
const code = token.content.trim()
return `<div class="dot">${code}</div>`
}
} catch (e) {
console.error(`Parse flowchart Error: `, e)
}
return temp(tokens, idx, options, env, slf)
}
}
export const renderDot = () => {
let list = document.querySelectorAll('.dot')
if (!list) {
return
}
var viz = new Viz();
list.forEach(item => {
viz.renderSVGElement(item.textContent).then(function (e) {
item.textContent = ''
item.appendChild(e)
})
.catch(e => {
var viz = new Viz();
console.error(`Parse dot Error: ${e}`)
})
})
list = null
}
export default dot

View File

@ -0,0 +1,39 @@
let options = {}
const flowchart = (md, opts = {}) => {
options = opts
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
try {
if (token.info && token.info.trim() === 'flowchart') {
const code = token.content.trim()
return `<div class="flowchart">${code}</div>`
}
} catch (e) {
console.error(`Parse flowchart Error: `, e)
}
return temp(tokens, idx, options, env, slf)
}
}
export const renderFlowchart = () => {
let list = document.querySelectorAll('div.flowchart')
if (!list) {
return
}
list.forEach(item => {
try {
let d = window.flowchart.parse(item.textContent);
item.className = ''
item.textContent = ''
d.drawSVG(item, options);
d = null
} catch (e) {
console.error(`Parse flowchart Error: ${e}`)
}
})
list = null
}
export default flowchart

View File

@ -0,0 +1,28 @@
function resolveHtmlImage (tokens, idx) {
let content = tokens[idx].content || ''
content = content.replace(/<img\s+([^>]*?)src\s*=\s*(["'])([^\2>]+?)\2([^>]*)>/gm, (m, g1, g2, g3, g4) => {
if (/^(http|\/\/|data:)/.test(g3)) {
return m
}
return `<img ${g1}src="/_local_image_${encodeURIComponent(g3)}"${g4}>`
})
return content
}
function resolveImage (tokens, idx) {
const src = tokens[idx].attrs[0][1]
const alt = tokens[idx].content
const resAttrs = tokens[idx].attrs.slice(2).reduce((pre, cur) => `${pre} ${cur[0]}=${cur[1]}`, '')
if (/^(http|\/\/|data:)/.test(src)) {
return `<img src="${src}" alt="${alt}" ${resAttrs} />`
}
return `<img src="/_local_image_${encodeURIComponent(src)}" alt="${alt}" ${resAttrs} />`
}
export default function localImage (md) {
md.renderer.rules.image = resolveImage
md.renderer.rules.html_block = resolveHtmlImage
md.renderer.rules.html_inline = resolveHtmlImage
}

View File

@ -0,0 +1,377 @@
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>
)
}
}

View File

@ -0,0 +1,207 @@
// fork from https://github.com/waylonflinn/markdown-it-katex
/* Process inline math */
/*
Like markdown-it-simplemath, this is a stripped down, simplified version of:
https://github.com/runarberg/markdown-it-math
It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX
for rendering output.
*/
/* jslint node: true */
'use strict'
// Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos]
function isValidDelim (state, pos) {
var prevChar; var nextChar
var max = state.posMax
var can_open = true
var can_close = true
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
// Check non-whitespace conditions for opening and closing, and
// check that closing delimeter isn't followed by a number
if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
(nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
can_close = false
}
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
can_open = false
}
return {
can_open: can_open,
can_close: can_close
}
}
function math_inline (state, silent) {
var start, match, token, res, pos, esc_count
if (state.src[state.pos] !== '$') { return false }
res = isValidDelim(state, state.pos)
if (!res.can_open) {
if (!silent) { state.pending += '$' }
state.pos += 1
return true
}
// First check for and bypass all properly escaped delimieters
// This loop will assume that the first leading backtick can not
// be the first character in state.src, which is known since
// we have found an opening delimieter already.
start = state.pos + 1
match = start
while ((match = state.src.indexOf('$', match)) !== -1) {
// Found potential $, look for escapes, pos will point to
// first non escape when complete
pos = match - 1
while (state.src[pos] === '\\') { pos -= 1 }
// Even number of escapes, potential closing delimiter found
if (((match - pos) % 2) == 1) { break }
match += 1
}
// No closing delimter found. Consume $ and continue.
if (match === -1) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
// Check if we have empty content, ie: $$. Do not parse.
if (match - start === 0) {
if (!silent) { state.pending += '$$' }
state.pos = start + 1
return true
}
// Check for valid closing delimiter
res = isValidDelim(state, match)
if (!res.can_close) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
if (!silent) {
token = state.push('math_inline', 'math', 0)
token.markup = '$'
token.content = state.src.slice(start, match)
}
state.pos = match + 1
return true
}
function math_block (state, start, end, silent) {
var firstLine; var lastLine; var next; var lastPos; var found = false; var token
var pos = state.bMarks[start] + state.tShift[start]
var max = state.eMarks[start]
if (pos + 2 > max) { return false }
if (state.src.slice(pos, pos + 2) !== '$$') { return false }
pos += 2
firstLine = state.src.slice(pos, max)
if (silent) { return true }
if (firstLine.trim().slice(-2) === '$$') {
// Single line expression
firstLine = firstLine.trim().slice(0, -2)
found = true
}
for (next = start; !found;) {
next++
if (next >= end) { break }
pos = state.bMarks[next] + state.tShift[next]
max = state.eMarks[next]
if (pos < max && state.tShift[next] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
break
}
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
lastPos = state.src.slice(0, max).lastIndexOf('$$')
lastLine = state.src.slice(pos, lastPos)
found = true
}
}
state.line = next + 1
token = state.push('math_block', 'math', 0)
token.block = true
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
state.getLines(start + 1, next, state.tShift[start], true) +
(lastLine && lastLine.trim() ? lastLine : '')
token.map = [ start, state.line ]
token.markup = '$$'
return true
}
export default function math_plugin (md, options) {
// Default options
options = options || {}
options.macros = options.macros || {}
// set KaTeX as the renderer for markdown-it-simplemath
var katexInline = function (latex) {
const opt = {
...options
}
if (opt.displayMode === undefined) {
opt.displayMode = false
}
try {
return katex.renderToString(latex, opt)
} catch (error) {
if (opt.throwOnError) { console.log(error) }
return latex
}
}
var inlineRenderer = function (tokens, idx) {
return katexInline(tokens[idx].content)
}
var katexBlock = function (latex) {
const opt = {
...options
}
if (opt.displayMode === undefined) {
opt.displayMode = true
}
try {
return '<p>' + katex.renderToString(latex, opt) + '</p>'
} catch (error) {
if (opt.throwOnError) { console.log(error) }
return latex
}
}
var blockRenderer = function (tokens, idx) {
return katexBlock(tokens[idx].content) + '\n'
}
md.inline.ruler.after('escape', 'math_inline', math_inline)
md.block.ruler.after('blockquote', 'math_block', math_block, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
md.renderer.rules.math_inline = inlineRenderer
md.renderer.rules.math_block = blockRenderer
}

View File

@ -0,0 +1,26 @@
/*
* https://github.com/digitalmoksha/markdown-it-inject-linenumbers/blob/master/index.js
*/
export default function injectLinenumbersPlugin (md) {
//
// Inject line numbers for sync scroll. Notes:
//
// - We track only headings and paragraphs, at any level.
// - TODO Footnotes content causes jumps. Level limit filters it automatically.
function injectLineNumbers (tokens, idx, options, env, slf) {
var line
// if (tokens[idx].map && tokens[idx].level === 0) {
if (tokens[idx].map) {
line = tokens[idx].map[0]
tokens[idx].attrJoin('class', 'source-line')
tokens[idx].attrSet('data-source-line', String(line))
}
return slf.renderToken(tokens, idx, options, env, slf)
}
md.renderer.rules.paragraph_open = injectLineNumbers
md.renderer.rules.heading_open = injectLineNumbers
md.renderer.rules.list_item_open = injectLineNumbers
md.renderer.rules.table_open = injectLineNumbers
}

View File

@ -0,0 +1,340 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["markdown-it-imsize.js"] = factory();
else
root["markdown-it-imsize.js"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
// Process ![test]( x =100x200)
// ^^^^^^^^ this size specification
'use strict';
var parseImageSize = __webpack_require__(1);
function image_with_size(md, options) {
return function(state, silent) {
var attrs,
code,
label,
labelEnd,
labelStart,
pos,
ref,
res,
title,
width = '',
height = '',
token,
tokens,
start,
href = '',
oldPos = state.pos,
max = state.posMax;
if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; }
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; }
labelStart = state.pos + 2;
labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false);
// parser failed to find ']', so it's not a valid link
if (labelEnd < 0) { return false; }
pos = labelEnd + 1;
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
//
// Inline link
//
// [link]( <href> "title" )
// ^^ skipping these spaces
pos++;
for (; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
if (pos >= max) { return false; }
// [link]( <href> "title" )
// ^^^^^^ parsing link destination
start = pos;
res = md.helpers.parseLinkDestination(state.src, pos, state.posMax);
if (res.ok) {
href = state.md.normalizeLink(res.str);
if (state.md.validateLink(href)) {
pos = res.pos;
} else {
href = '';
}
}
// [link]( <href> "title" )
// ^^ skipping these spaces
start = pos;
for (; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
// [link]( <href> "title" )
// ^^^^^^^ parsing link title
res = md.helpers.parseLinkTitle(state.src, pos, state.posMax);
if (pos < max && start !== pos && res.ok) {
title = res.str;
pos = res.pos;
// [link]( <href> "title" )
// ^^ skipping these spaces
for (; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
} else {
title = '';
}
// [link]( <href> "title" =WxH )
// ^^^^ parsing image size
if (pos - 1 >= 0) {
code = state.src.charCodeAt(pos - 1);
// there must be at least one white spaces
// between previous field and the size
if (code === 0x20) {
res = parseImageSize(state.src, pos, state.posMax);
if (res.ok) {
width = res.width;
height = res.height;
pos = res.pos;
// [link]( <href> "title" =WxH )
// ^^ skipping these spaces
for (; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
}
}
}
if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
state.pos = oldPos;
return false;
}
pos++;
} else {
//
// Link reference
//
if (typeof state.env.references === 'undefined') { return false; }
// [foo] [bar]
// ^^ optional whitespace (can include newlines)
for (; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
start = pos + 1;
pos = md.helpers.parseLinkLabel(state, pos);
if (pos >= 0) {
label = state.src.slice(start, pos++);
} else {
pos = labelEnd + 1;
}
} else {
pos = labelEnd + 1;
}
// covers label === '' and label === undefined
// (collapsed reference link and shortcut reference link respectively)
if (!label) { label = state.src.slice(labelStart, labelEnd); }
ref = state.env.references[md.utils.normalizeReference(label)];
if (!ref) {
state.pos = oldPos;
return false;
}
href = ref.href;
title = ref.title;
}
//
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
//
if (!silent) {
state.pos = labelStart;
state.posMax = labelEnd;
var newState = new state.md.inline.State(
state.src.slice(labelStart, labelEnd),
state.md,
state.env,
tokens = []
);
newState.md.inline.tokenize(newState);
token = state.push('image', 'img', 0);
token.attrs = attrs = [ [ 'src', href ],
[ 'alt', '' ] ];
token.children = tokens;
if (title) {
attrs.push([ 'title', title ]);
}
if (width !== '') {
attrs.push([ 'width', width ]);
}
if (height !== '') {
attrs.push([ 'height', height ]);
}
}
state.pos = pos;
state.posMax = max;
return true;
};
}
module.exports = function imsize_plugin(md, options) {
md.inline.ruler.before('emphasis', 'image', image_with_size(md, options));
};
/***/ }),
/* 1 */
/***/ (function(module, exports) {
// Parse image size
//
'use strict';
function parseNextNumber(str, pos, max) {
var code,
start = pos,
result = {
ok: false,
pos: pos,
value: ''
};
code = str.charCodeAt(pos);
while (pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */) {
code = str.charCodeAt(++pos);
}
result.ok = true;
result.pos = pos;
result.value = str.slice(start, pos);
return result;
}
module.exports = function parseImageSize(str, pos, max) {
var code,
result = {
ok: false,
pos: 0,
width: '',
height: ''
};
if (pos >= max) { return result; }
code = str.charCodeAt(pos);
if (code !== 0x3d /* = */) { return result; }
pos++;
// size must follow = without any white spaces as follows
// (1) =300x200
// (2) =300x
// (3) =x200
code = str.charCodeAt(pos);
if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) {
return result;
}
// parse width
var resultW = parseNextNumber(str, pos, max);
pos = resultW.pos;
// next charactor must be 'x'
code = str.charCodeAt(pos);
if (code !== 0x78 /* x */) { return result; }
pos++;
// parse height
var resultH = parseNextNumber(str, pos, max);
pos = resultH.pos;
result.width = resultW.value;
result.height = resultH.value;
result.pos = pos;
result.ok = true;
return result;
};
/***/ })
/******/ ])
});
;

View File

@ -0,0 +1,35 @@
import {escape} from './utils';
/*
* global mermaid
*/
const mermaidChart = (code) => {
try {
// eslint-disable-next-line
mermaid.parse(code)
return `<div class="mermaid">${escape(code)}</div>`
} catch ({ str, hash }) {
return `<pre>${str}</pre>`
}
}
const MermaidPlugin = (md) => {
const origin = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
const code = token.content.trim()
if (typeof token.info === 'string' && token.info.trim() === 'mermaid') {
return mermaidChart(code)
}
const firstLine = code.split(/\n/)[0].trim()
if (firstLine === 'gantt' ||
firstLine === 'sequenceDiagram' ||
firstLine === 'erDiagram' ||
firstLine.match(/^graph (?:TB|BT|RL|LR|TD);?$/)) {
return mermaidChart(code)
}
return origin(tokens, idx, options, env, slf)
}
}
export default MermaidPlugin

View File

@ -0,0 +1,25 @@
import getRender from 'md-it-meta/lib/meta'
export const meta = separates => {
if (separates === void 0) {
separates = [['---'], ['---']]
}
return (md) => {
const render = getRender(md, separates)
md.meta = md.meta || {}
md.block.ruler.before(
'code',
'meta',
(...args) => {
try {
return render(...args)
} catch(e) {
console.log('md-it-meta', e)
}
},
{
alt: []
}
);
};
}

View File

@ -0,0 +1,26 @@
const plantumlEncoder = require("plantuml-encoder");
function generateSourceDefault (umlCode, pluginOptions) {
var imageFormat = pluginOptions.imageFormat || 'img'
var diagramName = pluginOptions.diagramName || 'uml'
var server = pluginOptions.server || 'https://www.plantuml.com/plantuml'
var zippedCode = plantumlEncoder.encode(umlCode)
return server + '/' + imageFormat + '/' + zippedCode
}
export default (md, opts = {}) => {
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
try {
if (token.info && token.info.indexOf('plantuml') != -1 ) {
const code = token.content.trim()
return `<img src="${generateSourceDefault(code, opts)}" alt="" />`
}
} catch (e) {
console.error(`Parse Diagram Error: `, e)
}
return temp(tokens, idx, options, env, slf)
}
}

View File

@ -0,0 +1,112 @@
function scroll (offsetTop) {
[document.body, document.documentElement].forEach((ele) => {
// eslint-disable-next-line
TweenLite.to(
ele,
0.4,
{
scrollTop: offsetTop,
ease: Power2.easeOut // eslint-disable-line
}
)
})
}
function getAttrTag (line) {
return `[data-source-line="${line}"]`
}
function getPreLineOffsetTop (line) {
let currentLine = line - 1
let ele = null
while (currentLine > 0 && !ele) {
ele = document.querySelector(getAttrTag(currentLine))
if (!ele) {
currentLine -= 1
}
}
return [
currentLine >= 0 ? currentLine : 0,
ele ? ele.offsetTop : 0
]
}
function getNextLineOffsetTop (line, len) {
let currentLine = line + 1
let ele = null
while (currentLine < len && !ele) {
ele = document.querySelector(getAttrTag(currentLine))
if (!ele) {
currentLine += 1
}
}
return [
currentLine < len ? currentLine : len - 1,
ele ? ele.offsetTop : document.documentElement.scrollHeight
]
}
function topOrBottom (line, len) {
if (line === 0) {
scroll(0)
} else if (line === len - 1) {
scroll(document.documentElement.scrollHeight)
}
}
function relativeScroll (line, ratio, len) {
let offsetTop = 0
const lineEle = document.querySelector(`[data-source-line="${line}"]`)
if (lineEle) {
offsetTop = lineEle.offsetTop
} else {
const pre = getPreLineOffsetTop(line)
const next = getNextLineOffsetTop(line, len)
offsetTop = pre[1] + ((next[1] - pre[1]) * (line - pre[0]) / (next[0] - pre[0]))
}
scroll(offsetTop - document.documentElement.clientHeight * ratio)
}
export default {
relative: function ({
cursor,
winline,
winheight,
len
}) {
const line = cursor - 1
const ratio = winline / winheight
if (line === 0 || line === len - 1) {
topOrBottom(line, len)
} else {
relativeScroll(line, ratio, len)
}
},
middle: function ({
cursor,
// winline,
// winheight,
len
}) {
const line = cursor - 1
if (line === 0 || line === len - 1) {
topOrBottom(line, len)
} else {
relativeScroll(line, 0.5, len)
}
},
top: function ({
cursor,
winline,
// winheight,
len
}) {
let line = cursor - 1
if (line === 0 || line === len - 1) {
topOrBottom(line, len)
} else {
line = cursor - winline
relativeScroll(line, 0, len)
}
}
}

View File

@ -0,0 +1,6 @@
export const escape = (str) => {
// escape html content
const d = document.createElement('div')
d.appendChild(document.createTextNode(str))
return d.innerHTML
}