Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions apps/weixin-md/components/RednoteEditor/RednoteEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import React, { useState } from "react";
import { MarkdownEditor, Preview } from "ui";
import { defaultTheme, WXRenderer } from "md-converter";
import { MarkdownEditor, RednotePreview } from "ui";
import { RednoteRenderer, rednoteTheme } from "md-converter";
import { marked } from "marked";

export const RednoteEditor = () => {
const renderer = new WXRenderer({ theme: defaultTheme });
const renderer = new RednoteRenderer({ theme: rednoteTheme });
const output = renderer.assemble();
marked.use({ renderer: output });
const [preview, setPreview] = useState("");
const [previews, setPreviews] = useState<string[]>([]);
const handleChange = (value: string) => {
const content = marked.parse(value);
const suffix = renderer.buildSuffix();
setPreview(content + suffix);
setPreviews(content.split("<hr />"));
};
return (
<div className="mx-auto flex max-w-screen-xl w-full h-full">
<div className="border border-zinc-200 w-3/5">
<MarkdownEditor onChange={handleChange}></MarkdownEditor>
</div>
<div className="border border-zinc-200 border-l-0 w-2/5">
<Preview preview={preview}></Preview>
<div className="border border-zinc-200 border-l-0 w-2/5 overflow-y-auto">
<RednotePreview previews={previews}></RednotePreview>
</div>
</div>
);
Expand Down
22 changes: 20 additions & 2 deletions apps/weixin-md/components/RednoteHeader/RednoteHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import html2canvas from "html2canvas";

export const RednoteHeader = () => {
const handleDownloadImages = () => {};
const handleDownloadImages = async () => {
const previewElements = document.querySelectorAll(".preview-wrap > div");

for (let i = 0; i < previewElements.length; i++) {
const element = previewElements[i] as HTMLElement;

const canvas = await html2canvas(element, {
scale: 3,
});

// document.body.appendChild(canvas);
const link = document.createElement("a");
link.download = `preview-${i + 1}.png`;
link.href = canvas.toDataURL();
link.click();
}
};

return (
<header aria-label="Page Header">
Expand Down Expand Up @@ -34,11 +52,11 @@ export const RednoteHeader = () => {
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
Expand Down
1 change: 1 addition & 0 deletions apps/weixin-md/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"html2canvas": "^1.4.1",
"marked": "4.2.3",
"md-converter": "workspace:^",
"next": "13.0.6",
Expand Down
7 changes: 7 additions & 0 deletions apps/weixin-md/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
@tailwind components;
@tailwind utilities;

/* fix text shifting issue with https://github.com/niklasvh/html2canvas/issues/2775 */
@layer base {
img {
@apply inline-block;
}
}

.md-editor {
height: 100% !important;
}
Expand Down
29 changes: 29 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/RednoteRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Renderer } from "marked";
import { BaseRenderer } from "../BaseRenderer";
import { codeConverterFactory } from "./converters/code";
import { codespanConverterFactory } from "./converters/codespan";
import { emConverterFactory } from "./converters/em";
import { headingConverterFactory } from "./converters/heading";
import { hrConverterFactory } from "./converters/hr";
import { linkConverterFactory } from "./converters/link";
import { listConverterFactory } from "./converters/list";
import { listItemConverterFactory } from "./converters/listItem";
import { paragraphConverterFactory } from "./converters/paragraph";
import { strongConverterFactory } from "./converters";

export class RednoteRenderer extends BaseRenderer {
assemble(): Partial<Renderer> {
return {
em: emConverterFactory(this.theme),
heading: headingConverterFactory(this.theme),
hr: hrConverterFactory(this.theme),
link: linkConverterFactory(this.theme, {}),
list: listConverterFactory(this.theme),
listitem: listItemConverterFactory(this.theme),
paragraph: paragraphConverterFactory(this.theme),
code: codeConverterFactory(this.theme),
codespan: codespanConverterFactory(this.theme),
strong: strongConverterFactory(this.theme),
};
}
}
32 changes: 32 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import hljs from "highlight.js";
import "highlight.js/styles/github.css";
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const codeConverter: ConverterFunc<MarkdownElement.Code> = (
styles: Theme,
text: string,
lang: string,
) => {
lang = hljs.getLanguage(lang) ? lang : "plaintext";

text = hljs.highlight(text, { language: lang }).value;

text = text
.replace(/\r\n/g, "<br/>")
.replace(/\n/g, "<br/>")
.replace(/(>[^<]+)|(^[^<]+)/g, (str) => {
return str.replace(/\s/g, "&nbsp;");
});

return `<pre class="hljs code__pre" style="${makeStyleText(
styles.code.pre,
)}"><code class="prettyprint language-${lang}" style="${makeStyleText(
styles.code.code,
)}">${text}</code></pre>`;
};

export const codeConverterFactory = (styles: Theme) => {
return (text: string, lang: string) => codeConverter(styles, text, lang);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const codespanConverter: ConverterFunc<MarkdownElement.CodeSpan> = (
styles: Theme,
text: string,
) => {
return `<code style="${makeStyleText(
styles[MarkdownElement.CodeSpan],
)}">${text}</code>`;
};

export const codespanConverterFactory = (styles: Theme) => {
return (text: string) => codespanConverter(styles, text);
};
16 changes: 16 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/em.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const emConverter: ConverterFunc<MarkdownElement.EM> = (
styles: Theme,
text: string,
) => {
return `<em style="${makeStyleText(
styles[MarkdownElement.EM],
)}">${text}</em>`;
};

export const emConverterFactory = (styles: Theme) => {
return (text: string) => emConverter(styles, text);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const headingConverter: ConverterFunc<MarkdownElement.Heading> = (
styles: Theme,
text: string,
level: number,
) => {
const tag =
`h${Math.min(Math.max(level, 1), 6)}` as keyof (typeof styles)[MarkdownElement.Heading];
return `<${tag} style="${makeStyleText(
styles[MarkdownElement.Heading][tag],
)}">${text}</${tag}>`;
};

export const headingConverterFactory = (styles: Theme) => {
return (text: string, level: number) => headingConverter(styles, text, level);
};
12 changes: 12 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/hr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";

export const hrConverter: ConverterFunc<MarkdownElement.HR> = (
styles: Theme,
) => {
return `<hr />`;
};

export const hrConverterFactory = (styles: Theme) => {
return () => hrConverter(styles);
};
10 changes: 10 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from "./code";
export * from "./codespan";
export * from "./em";
export * from "./heading";
export * from "./hr";
export * from "./link";
export * from "./list";
export * from "./listItem";
export * from "./paragraph";
export * from "./strong";
28 changes: 28 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Theme } from "../../../themes";
import {
MarkdownElement,
ConverterFunc,
LinkConverterOptions,
} from "../../../types";
import { makeStyleText } from "../../../utils";

export const linkConverter: ConverterFunc<MarkdownElement.Link> = (
styles: Theme,
options: LinkConverterOptions,
href: string,
title: string,
text: string,
) => {
const titleAttr = title ? ` title="${title}"` : "";
return `<a href="${href}"${titleAttr} style="${makeStyleText(
styles[MarkdownElement.Link],
)}">${text}</a>`;
};

export const linkConverterFactory = (
styles: Theme,
options: LinkConverterOptions,
) => {
return (href: string, title: string, text: string) =>
linkConverter(styles, options, href, title, text);
};
40 changes: 40 additions & 0 deletions packages/md-converter/renderer/RednoteRenderer/converters/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const listConverter: ConverterFunc<MarkdownElement.List> = (
styles: Theme,
body: string,
ordered: boolean,
_start: number,
) => {
body = body.replace(/<\/*p.*?>/g, "");
// Split on li tags and filter out empty strings
let segments = body.split(/<\/?li[^>]*>/g).filter((s) => s.trim());
if (!ordered) {
// Wrap each segment in li tags
body = segments
.map(
(s) =>
`<li style="${makeStyleText(
styles[MarkdownElement.ListItem],
)}"><span style="font-size: 20px;${makeStyleText(styles[MarkdownElement.ListItemSymbol])}">•</span>${s}</li>`,
)
.join("");
return `<ul style="${makeStyleText(styles[MarkdownElement.List].ul)}">${body}</ul>`;
}
body = segments
.map(
(s, i) =>
`<li style="${makeStyleText(
styles[MarkdownElement.ListItem],
)}"><span style="${makeStyleText(styles[MarkdownElement.ListItemSymbol])}">${i + 1}.</span>${s}</li>`,
)
.join("");
return `<ol style="${makeStyleText(styles[MarkdownElement.List].ol)}">${body}</ol>`;
};

export const listConverterFactory = (styles: Theme) => {
return (body: string, ordered: boolean, start: number) =>
listConverter(styles, body, ordered, start);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const listItemConverter: ConverterFunc<MarkdownElement.ListItem> = (
styles: Theme,
text: string,
task: boolean,
checked: boolean,
) => {
let checkbox = "";
if (task) {
checkbox = `<input type="checkbox" ${checked ? "checked" : ""} disabled /> `;
}
return `<li style="${makeStyleText(
styles[MarkdownElement.ListItem],
)}">${checkbox}${text}</li>`;
};

export const listItemConverterFactory = (styles: Theme) => {
return (text: string, task: boolean, checked: boolean) =>
listItemConverter(styles, text, task, checked);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const paragraphConverter: ConverterFunc<MarkdownElement.Paragraph> = (
styles: Theme,
text: string,
) => {
return `<p style="${makeStyleText(
styles[MarkdownElement.Paragraph],
)}">${text}</p>`;
};

export const paragraphConverterFactory = (styles: Theme) => {
return (text: string) => paragraphConverter(styles, text);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Theme } from "../../../themes";
import { MarkdownElement, ConverterFunc } from "../../../types";
import { makeStyleText } from "../../../utils";

export const strongConverter: ConverterFunc<MarkdownElement.Strong> = (
styles: Theme,
text: string,
) => {
return `<strong style="${makeStyleText(styles.strong)}">${text}</strong>`;
};

export const strongConverterFactory = (styles: Theme) => {
return (text: string) => strongConverter(styles, text);
};
1 change: 1 addition & 0 deletions packages/md-converter/renderer/RednoteRenderer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./RednoteRenderer";
6 changes: 3 additions & 3 deletions packages/md-converter/renderer/WXRenderer/WXRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export class WXRenderer extends BaseRenderer {
return `<code style="font-size: 90%; opacity: 0.6;">[${
index + 1
}]</code>: <em style="${makeStyleText(
this.theme.footNotes.item,
this.theme.footNotes?.item,
)}">${title}</em><br/>`;
}
return `<code style="font-size: 90%; opacity: 0.6;">[${
index + 1
}]</code><em style="${makeStyleText(
this.theme.footNotes.item,
this.theme.footNotes?.item,
)}">${title}: ${href}</em><br/>`;
});
if (notes.length === 0) {
Expand All @@ -49,7 +49,7 @@ export class WXRenderer extends BaseRenderer {
return `<h4 style="${makeStyleText(
this.theme.heading.h4,
)}">相关引用</h4><p style="${makeStyleText(
this.theme.footNotes.container,
this.theme.footNotes?.container,
)}">${notes.join("\n")}</p>`;
};

Expand Down
1 change: 1 addition & 0 deletions packages/md-converter/renderer/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./WXRenderer";
export * from "./RednoteRenderer";
1 change: 1 addition & 0 deletions packages/md-converter/themes/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./default";
export * from "./rednote";
export * from "./types";
Loading
Loading