Skip to content

Commit

Permalink
feat(md-enhance): add flowchart back
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Jan 28, 2022
1 parent da60f1d commit d870212
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/md-enhance/package.json
Expand Up @@ -59,6 +59,7 @@
"@vuepress/plugin-container": "2.0.0-beta.35",
"@vuepress/shared": "2.0.0-beta.35",
"@vuepress/utils": "2.0.0-beta.35",
"flowchart.js": "^1.17.0",
"katex": "^0.15.2",
"markdown-it": "^12.3.2",
"mermaid": "^8.13.10",
Expand Down
7 changes: 5 additions & 2 deletions packages/md-enhance/src/client/appEnhance.ts
@@ -1,6 +1,7 @@
import { defineClientAppEnhance } from "@vuepress/client";
import CodeGroup from "@CodeGroup";
import CodeGroupItem from "@CodeGroupItem";
import FlowChart from "@FlowChart";
import Mermaid from "@Mermaid";
import Presentation from "@Presentation";

Expand All @@ -19,13 +20,15 @@ export default defineClientAppEnhance(({ app }) => {

if (MARKDOWN_ENHANCE_FOOTNOTE) void import("./styles/footnote.scss");

if (Mermaid.name) app.component("MermaidChart", Mermaid);

if (CodeGroup.name) {
app.component("CodeGroup", CodeGroup);
app.component("CodeGroupItem", CodeGroupItem);
}

if (FlowChart.name) app.component("FlowChart", FlowChart);

if (Mermaid.name) app.component("MermaidChart", Mermaid);

if (Presentation.name) {
app.component("PresentationViewer", Presentation);
void import("./styles/slides.scss");
Expand Down
105 changes: 105 additions & 0 deletions packages/md-enhance/src/client/components/FlowChart.ts
@@ -0,0 +1,105 @@
import {
computed,
defineComponent,
h,
onBeforeUnmount,
onMounted,
ref,
} from "vue";
import debounce from "lodash.debounce";
import presets from "../flowchart-preset";

import type * as Flowchart from "flowchart.js";
import type { PropType, VNode } from "vue";

import "../styles/flowchart.scss";

declare const MARKDOWN_ENHANCE_DELAY: number;

export default defineComponent({
name: "FlowChart",

props: {
id: { type: String, required: true },
preset: {
type: String as PropType<"ant" | "vue">,
default: "vue",
},
},

setup(props) {
let svg: Flowchart.Instance;
let resize: () => void;
const element = ref<HTMLDivElement | null>(null);

const loading = ref(true);
const scale = ref(1);

const preset = computed<Record<string, unknown>>(() => {
const preset = presets[props.preset];

if (!preset) {
console.warn(`[md-enhance:flowchart] Unknown preset: ${props.preset}`);

return presets.vue;
}

return preset;
});

const getScale = (width: number): number =>
width < 419 ? 0.8 : width > 1280 ? 1 : 0.9;

onMounted(() => {
element.value?.setAttribute("id", props.id);

void Promise.all([
import(/* webpackChunkName: "flowchart" */ "flowchart.js"),
// delay
new Promise((resolve) => setTimeout(resolve, MARKDOWN_ENHANCE_DELAY)),
]).then(([flowchart]) => {
const { parse } = flowchart;

svg = parse(decodeURIComponent(element.value?.dataset.code || ""));

// update scale
scale.value = getScale(window.innerWidth);

loading.value = false;

// draw svg to #id
svg.drawSVG(props.id, { ...preset.value, scale: scale.value });

resize = debounce(() => {
const newScale = getScale(window.innerWidth);

if (scale.value !== newScale) {
scale.value = newScale;

svg.drawSVG(props.id, { ...preset.value, scale: newScale });
}
}, 100);

window.addEventListener("resize", resize);
});
});

onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});

return (): VNode =>
h("div", {
ref: element,
class: [
"flowchart-wrapper",
{
loading: loading.value,
innerHTML: loading.value
? '<svg xmlns="http://www.w3.org/2000/svg" class="loading-icon" style="background:0 0;display:block;shape-rendering:auto" width="200" height="200" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" r="0" fill="none" stroke="currentColor" stroke-width="2"><animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;40" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="0s"/><animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="0s"/></circle><circle cx="50" cy="50" r="0" fill="none" stroke="currentColor" stroke-width="2"><animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;40" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="-0.3333333333333333s"/><animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="-0.3333333333333333s"/></circle><circle cx="50" cy="50" r="0" fill="none" stroke="currentColor" stroke-width="2"><animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;40" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="-0.6666666666666666s"/><animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="-0.6666666666666666s"/></circle></svg>'
: "",
},
],
});
},
});
53 changes: 53 additions & 0 deletions packages/md-enhance/src/client/flowchart-preset/ant.ts
@@ -0,0 +1,53 @@
import base from "./base";

export default {
...base,
...{
// style symbol types
symbols: {
start: {
class: "start-element",
"font-color": "#fff",
fill: "#595959",
"line-width": "0px",
},
end: {
class: "end-element",
"font-color": "#fff",
fill: "#595959",
"line-width": "0px",
},
operation: {
class: "operation-element",
"font-color": "#fff",
fill: "#1890ff",
"line-width": "0px",
},
inputoutput: {
class: "inputoutput-element",
"font-color": "#fff",
fill: "#1890ff",
"line-width": "0px",
},
subroutine: {
class: "subroutine-element",
"font-color": "#fff",
fill: "#FF485E",
"element-color": "#fff",
"line-color": "red",
},
condition: {
class: "condition-element",
"font-color": "#fff",
fill: "#FF485E",
"line-width": "0px",
},
parallel: {
class: "parallel-element",
"font-color": "#fff",
fill: "#1890ff",
"line-width": "0px",
},
},
},
};
18 changes: 18 additions & 0 deletions packages/md-enhance/src/client/flowchart-preset/base.ts
@@ -0,0 +1,18 @@
export default {
// eslint-disable-next-line id-length
x: 0,
// eslint-disable-next-line id-length
y: 0,
"line-width": 2,
"line-length": 40,
"text-margin": 8,
"font-size": 14,
"font-color": "#8DA1AC",
"line-color": "#8DA1AC",
"element-color": "black",
fill: "white",
"yes-text": "Yes",
"no-text": "No",
"arrow-end": "block",
scale: 1,
};
7 changes: 7 additions & 0 deletions packages/md-enhance/src/client/flowchart-preset/index.ts
@@ -0,0 +1,7 @@
import ant from "./ant";
import vue from "./vue";

export default {
ant,
vue,
};
53 changes: 53 additions & 0 deletions packages/md-enhance/src/client/flowchart-preset/vue.ts
@@ -0,0 +1,53 @@
import base from "./base";

export default {
...base,
...{
// style symbol types
symbols: {
start: {
class: "start-element",
"font-color": "#fff",
fill: "#2F495F",
"line-width": "0px",
},
end: {
class: "end-element",
"font-color": "#fff",
fill: "#2F495F",
"line-width": "0px",
},
operation: {
class: "operation-element",
"font-color": "#fff",
fill: "#00BC7D",
"line-width": "0px",
},
inputoutput: {
class: "inputoutput-element",
"font-color": "#fff",
fill: "#EB4D5D",
"line-width": "0px",
},
subroutine: {
class: "subroutine-element",
"font-color": "#fff",
fill: "#937AC4",
"element-color": "#fff",
"line-color": "red",
},
condition: {
class: "condition-element",
"font-color": "#fff",
fill: "#FFB500",
"line-width": "0px",
},
parallel: {
class: "parallel-element",
"font-color": "#fff",
fill: "#2F495F",
"line-width": "0px",
},
},
},
};
33 changes: 33 additions & 0 deletions packages/md-enhance/src/client/styles/flowchart.scss
@@ -0,0 +1,33 @@
@use "@hope/config";

.flowchart-wrapper {
padding: 0.6em 0.4em;
transition: all 1s;
text-align: center;
overflow-x: scroll;

&.loading {
display: flex;
justify-content: center;
align-items: center;
background: var(--grey15);
}

@media (max-width: config.$mobile) {
margin: 0 -1.5rem;
padding: 0.6em 0;
}

svg.loading-icon {
width: 4em;
height: 4em;
margin: 2.5em auto;
color: var(--c-brand);
}
}

.operation-element,
.parallel-element {
rx: 5px;
ry: 5px;
}
34 changes: 34 additions & 0 deletions packages/md-enhance/src/node/markdown-it/flowchart.ts
@@ -0,0 +1,34 @@
/* eslint-disable max-statements */
import { hash } from "@vuepress/utils";
import type Token from "markdown-it/lib/token";

import type MarkdownIt from "markdown-it";

const flowchartRender = (tokens: Token[], idx: number): string => {
const token = tokens[idx];
const key = `flowchart-${hash(idx)}`;
const { content, info } = token;

return `<FlowChart id="${key}" data-code="${encodeURIComponent(
content
)}" preset="${info.trim().split(":")[1] || "vue"}"></FlowChart>`;
};

export const flowchart = (md: MarkdownIt): void => {
// Handle ```flow and ```flowchart blocks
const fence = md.renderer.rules.fence;

md.renderer.rules.fence = (...args): string => {
const [tokens, idx] = args;
const { info } = tokens[idx];
const realInfo = info.trim().split(":")[0];

if (realInfo === "flow" || realInfo === "flowchart")
return flowchartRender(tokens, idx);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return fence!(...args);
};

md.renderer.rules.flowchart = flowchartRender;
};
1 change: 1 addition & 0 deletions packages/md-enhance/src/node/markdown-it/index.ts
@@ -1,5 +1,6 @@
export * from "./code-demo";
export * from "./details";
export * from "./flowchart";
export * from "./footnote";
export * from "./katex";
export * from "./lazy-load";
Expand Down
7 changes: 7 additions & 0 deletions packages/md-enhance/src/node/plugin.ts
Expand Up @@ -3,6 +3,7 @@ import { path } from "@vuepress/utils";
import { useSassPalettePlugin } from "vuepress-plugin-sass-palette";
import {
codeDemoDefaultSetting,
flowchart,
footnote,
katex,
lazyLoad,
Expand All @@ -26,6 +27,7 @@ export const mdEnhancePlugin: Plugin<MarkdownEnhanceOptions> = (
const containerEnable = options.enableAll || options.container || false;
const codegroupEnable = options.enableAll || options.codegroup || false;
const demoEnable = options.enableAll || options.demo || false;
const flowchartEnable = options.enableAll || options.flowchart || false;
const footnoteEnable = options.enableAll || options.footnote || false;
const tasklistEnable = options.enableAll || options.tasklist || false;
const mermaidEnable = options.enableAll || Boolean(options.mermaid) || false;
Expand All @@ -42,6 +44,7 @@ export const mdEnhancePlugin: Plugin<MarkdownEnhanceOptions> = (
if (app.env.isDev)
addViteOptimizeDeps(app, "@mr-hope/vuepress-shared/lib/client");

if (flowchartEnable) addViteOptimizeDeps(app, "flowchart.js");
if (mermaidEnable) addViteOptimizeDeps(app, "mermaid");
if (presentationEnable)
addViteOptimizeDeps(app, [
Expand Down Expand Up @@ -69,6 +72,9 @@ export const mdEnhancePlugin: Plugin<MarkdownEnhanceOptions> = (
"@CodeGroupItem": codegroupEnable
? path.resolve(__dirname, "../client/components/CodeGroupItem.js")
: noopModule,
"@FlowChart": flowchartEnable
? path.resolve(__dirname, "../client/components/FlowChart.js")
: noopModule,
"@Mermaid": mermaidEnable
? path.resolve(__dirname, "../client/components/Mermaid.js")
: noopModule,
Expand Down Expand Up @@ -115,6 +121,7 @@ export const mdEnhancePlugin: Plugin<MarkdownEnhanceOptions> = (
if (options.sup || options.enableAll) markdownIt.use(sup);
if (options.sub || options.enableAll) markdownIt.use(sub);
if (footnoteEnable) markdownIt.use(footnote);
if (flowchartEnable) markdownIt.use(flowchart);
if (options.mark || options.enableAll) markdownIt.use(mark);
if (tasklistEnable)
markdownIt.use(tasklist, [
Expand Down

0 comments on commit d870212

Please sign in to comment.