Skip to content
This repository has been archived by the owner on Jul 25, 2023. It is now read-only.

Commit

Permalink
release 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
FlysoftBeta committed Jul 19, 2023
1 parent f12e75b commit 71b988c
Show file tree
Hide file tree
Showing 70 changed files with 455 additions and 52 deletions.
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "qqntim-plugin-template",
"name": "qqntim-plugin-markdown",
"private": true,
"version": "2.0.0",
"version": "1.0.0",
"packageManager": "[email protected]",
"license": "MIT",
"scripts": {
Expand All @@ -17,15 +17,24 @@
"format": "rome format . --write"
},
"devDependencies": {
"@flysoftbeta/qqntim-typings": "^3.0.0",
"@flysoftbeta/qqntim-typings": "^3.1.2",
"@types/katex": "^0.16.1",
"@types/markdown-it": "^12.2.3",
"@types/node": "^20.4.2",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/sanitize-html": "^2.9.0",
"@yarnpkg/sdks": "^3.0.0-rc.48",
"esbuild": "^0.18.12",
"fs-extra": "^11.1.1",
"rome": "^12.1.3",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"dependencies": {
"html-entities": "^2.4.0",
"katex": "^0.16.8",
"markdown-it": "^13.0.1",
"sanitize-html": "^2.11.0"
}
}
Binary file added publish/fonts/KaTeX_AMS-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_AMS-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_AMS-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Bold.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Bold.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Bold.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Caligraphic-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Bold.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Bold.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Bold.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Fraktur-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Bold.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Bold.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Bold.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-BoldItalic.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-BoldItalic.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-BoldItalic.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Italic.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Italic.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Italic.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Main-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-BoldItalic.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-BoldItalic.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-BoldItalic.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-Italic.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-Italic.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Math-Italic.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Bold.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Bold.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Bold.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Italic.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Italic.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Italic.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_SansSerif-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Script-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Script-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Script-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size1-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size1-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size1-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size2-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size2-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size2-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size3-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size3-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size3-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size4-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size4-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Size4-Regular.woff2
Binary file not shown.
Binary file added publish/fonts/KaTeX_Typewriter-Regular.ttf
Binary file not shown.
Binary file added publish/fonts/KaTeX_Typewriter-Regular.woff
Binary file not shown.
Binary file added publish/fonts/KaTeX_Typewriter-Regular.woff2
Binary file not shown.
1 change: 1 addition & 0 deletions publish/katex.min.css

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions publish/qqntim.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"manifestVersion": "3.0",
"id": "template-plugin",
"name": "QQNTim 模板插件",
"description": "快速开始开发你自己的插件",
"id": "markdown",
"name": "Markdown",
"description": "支持显示 Markdown 消息并渲染 LaTeX 公式",
"version": "1.0.0",
"author": "Flysoft",
"injections": [
Expand Down
57 changes: 56 additions & 1 deletion publish/style.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
/* Put your stylesheet here. */
.message-content.markdown-enabled img {
vertical-align: bottom;
max-width: 100%;
}

.message-content.markdown-enabled img:not(.markdown-face) {
width: 200px;
}

.message-content.markdown-enabled {
font-size: initial !important;
}

.message-content.markdown-enabled h1,
.message-content.markdown-enabled h2 {
display: block !important;
margin-inline-start: 0px !important;
margin-inline-end: 0px !important;
font-weight: bold !important;
line-height: initial !important;
}

.message-content.markdown-enabled h1 {
font-size: 2em !important;
margin-block-start: 0.67em !important;
margin-block-end: 0.67em !important;
}

.message-content.markdown-enabled h2 {
font-size: 1.5em !important;
margin-block-start: 0.83em !important;
margin-block-end: 0.83em !important;
}

.message-content.markdown-enabled h3 {
font-size: 1.17em !important;
margin-block-start: 1em !important;
margin-block-end: 1em !important;
}

.message-content.markdown-enabled h4 {
font-size: initial !important;
margin-block-start: 1.33em !important;
margin-block-end: 1.33em !important;
}

.message-content.markdown-enabled h5 {
font-size: 0.83em !important;
margin-block-start: 1.67em !important;
margin-block-end: 1.67em !important;
}

.message-content.markdown-enabled h6 {
font-size: 0.67em !important;
margin-block-start: 2.33em !important;
margin-block-end: 2.33em !important;
}
14 changes: 5 additions & 9 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
export const id = "my-template-plugin" as const;
export const id = "markdown-plugin" as const;

export const defaults: PluginConfig = {
switchConfigItem: false,
anotherSwitchConfigItem: false,
inputConfigItem: "默认值",
dropdownConfigItem: "A",
renderEverything: false,
markdownFlags: "<md>,<markdown>",
};
export function getPluginConfig(config: Config | undefined) {
return Object.assign({}, defaults, config?.[id] || {});
}

export interface PluginConfig {
switchConfigItem: boolean;
anotherSwitchConfigItem: boolean;
inputConfigItem: string;
dropdownConfigItem: "A" | "B" | "C";
renderEverything: boolean;
markdownFlags: string;
}
export type Config = {
[X in typeof id]?: Partial<PluginConfig>;
Expand Down
103 changes: 98 additions & 5 deletions src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,103 @@
import { getPluginConfig } from "./config";
import * as qqntim from "qqntim/renderer";
import MarkdownIt from "markdown-it";
import sanitizeHtml from "sanitize-html";
import { PluginConfig, getPluginConfig } from "./config";
import { env, utils } from "qqntim/renderer";
import { s } from "./utils/sep";
import { decode } from "html-entities";
import katex, { KatexOptions } from "katex";
import { randomUUID } from "crypto";

export default class Entry implements QQNTim.Entry.Renderer {
private md = new MarkdownIt({ html: true });
private config: PluginConfig;
constructor() {
const config = getPluginConfig(qqntim.env.config.plugins.config);
console.log("[Template] Hello world!", qqntim);
console.log("[Template] 当前插件配置:", config);
this.config = getPluginConfig(env.config.plugins.config);
}
private renderMarkdown(text: string) {
text = text.trim();

// 搜索 TeX 标签
const expressions = new Map<string, [string, KatexOptions]>();
for (const tag of ["$$", "$"]) {
let texStart = 0;
do {
texStart = text.indexOf(tag, texStart);
const texEnd = text.indexOf(tag, texStart + tag.length);
if (texEnd == -1) break;
const expression = decode(text.substring(texStart + tag.length, texEnd), { level: "html5" });
const id = randomUUID();
expressions.set(id, [expression, { throwOnError: false, displayMode: tag == "$$" }]);

text = `${text.slice(0, texStart)}TEX-${id}${text.slice(texEnd + tag.length)}`;
texStart = texEnd + tag.length;
} while (texStart != -1);
}

const rawHtml = this.md.render(text);

// 过滤不安全的 HTML 标签
let safeHtml = sanitizeHtml(rawHtml, {
allowedSchemes: ["appimg"],
allowedTags: [...sanitizeHtml.defaults.allowedTags, "img"],
allowedAttributes: {
...sanitizeHtml.defaults.allowedAttributes,
img: [...sanitizeHtml.defaults.allowedAttributes.img, "draggable"],
},
allowedClasses: {
img: ["markdown-face"],
},
allowVulnerableTags: false,
});

// 渲染并应用 TeX 标签
for (const [id, [expression, options]] of expressions) {
safeHtml = safeHtml.replace(`TEX-${id}`, katex.renderToString(expression, options));
}
return safeHtml;
}
onWindowLoaded(): void {
// 添加 KaTeX 相关样式
const style = document.createElement("link");
style.href = encodeURI(`file://${__dirname.replaceAll(s, "/")}/katex.min.css`);
style.type = "text/css";
style.rel = "stylesheet";
document.head.appendChild(style);

// 获取表情资源路径
utils
.ntCall("ns-ntApi", "nodeIKernelMsgService/getEmojiResourcePath", [{ type: 1 }])
.then((args: any) => args.resourcePath.replaceAll(s, "/") as string)
.then((emojiResDir) => {
new MutationObserver(() => {
const elements = document.querySelectorAll<HTMLElement>(".message-content:not(.markdown-patched)");
for (const element of elements) {
element.classList.add("markdown-patched");
const msg = element.__VUE__?.[0]?.props?.msgRecord;
if (msg) {
const msgElements = msg?.elements;

// 判断消息是否需要渲染
const flags = this.config.markdownFlags.split(",");
let isMarkdown = this.config.renderEverything;
if (!isMarkdown) for (const msgElement of msgElements) if (msgElement) for (const flag of flags) if (msgElement?.textElement?.content?.includes(flag)) isMarkdown = true;
if (!isMarkdown) continue;
element.classList.add("markdown-enabled");

// 将消息中携带的图片和表情全部转换为 HTML 标签
let text = "";
for (const msgElement of msgElements)
if (msgElement)
if (msgElement?.textElement) {
let content = msgElement.textElement.content as string;
for (const flag of flags) content = content.replaceAll(flag, "");
text += content;
} else if (msgElement?.picElement) text += `<img src="${encodeURI(`appimg://${msgElement.picElement.sourcePath}`)}" alt="图片" draggable="true">`;
else if (msgElement?.faceElement) text += `<img class="markdown-face" src="appimg://${emojiResDir}/gif/s${msgElement.faceElement.faceIndex}.gif" alt="${msgElement.faceElement.faceText || "表情"}" width="24px" draggable="false">`;

element.innerHTML = this.renderMarkdown(text);
}
}
}).observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true });
});
}
}
38 changes: 14 additions & 24 deletions src/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,39 @@
import { usePluginConfig } from "./utils/hooks";
import { defineSettingsPanels } from "qqntim-settings";
import { Dropdown, Input, SettingsBox, SettingsBoxItem, SettingsSection, Switch } from "qqntim-settings/components";
import { Input, SettingsBox, SettingsBoxItem, SettingsSection, Switch } from "qqntim-settings/components";
import { env } from "qqntim/renderer";
import { useMemo } from "react";
import { getPluginConfig } from "./config";

export default class Entry implements QQNTim.Entry.Renderer {
constructor() {
// 如果不需要设置界面,将下一行注释掉即可;如果需要在设置项目旁边加一个小图标,请将 `undefined` 改为一段 HTML 代码(可以是 `<svg>`, `<img>` 等等)。
defineSettingsPanels(["模板插件设置", SettingsPanel, undefined]);
defineSettingsPanels(["Markdown 渲染插件", SettingsPanel, undefined]);
}
}

function SettingsPanel({ config: _config, setConfig: _setConfig }: QQNTim.Settings.PanelProps) {
const [pluginConfig, setPluginConfig] = usePluginConfig(_config, _setConfig);
const currentPluginConfigString = useMemo(() => JSON.stringify(getPluginConfig(env.config.plugins.config)), []);
const currentPluginConfig = useMemo(() => getPluginConfig(env.config.plugins.config), []);

return (
<>
<SettingsSection title="使用方法">
<SettingsBox>
<SettingsBoxItem title="Markdown 消息" description={[`在消息头部插入 ${currentPluginConfig.markdownFlags.split(",").join(" 或 ")} 即可使用 Markdown 渲染你的消息。`]} />
<SettingsBoxItem title="LaTeX 渲染" description={["在 Markdown 消息中将公式使用 $ 包裹以使用内联模式(Inline Mode)显示,或将公式使用 $$ 包裹以使用外显模式(Display Mode)显示。"]} />
</SettingsBox>
</SettingsSection>
<SettingsSection title="插件设置">
<SettingsBox>
<SettingsBoxItem title="当前生效的插件配置:" description={[currentPluginConfigString]} />
<SettingsBoxItem title="开关" description={["这是一个开关。", `当前状态为:${pluginConfig.switchConfigItem ? "开" : "关"}`]}>
<Switch checked={pluginConfig.switchConfigItem} onToggle={(state) => setPluginConfig("switchConfigItem", state)} />
<SettingsBoxItem title="为所有消息启用" description={["将所有消息都视为 Markdown 消息(不推荐)。"]} isLast={pluginConfig.renderEverything}>
<Switch checked={pluginConfig.renderEverything} onToggle={(state) => setPluginConfig("renderEverything", state)} />
</SettingsBoxItem>
{pluginConfig.switchConfigItem && (
<SettingsBoxItem title="另一个开关" description={["这是另一个开关。", `当前状态为:${pluginConfig.anotherSwitchConfigItem ? "开" : "关"}`]}>
<Switch checked={pluginConfig.anotherSwitchConfigItem} onToggle={(state) => setPluginConfig("anotherSwitchConfigItem", state)} />
{!pluginConfig.renderEverything && (
<SettingsBoxItem title="Markdown 标记" description={["包含此标记的消息将会被视为 Markdown 消息。", "支持多个标记,请使用英文逗号(,)分割。"]} isLast={true}>
<Input value={pluginConfig.markdownFlags} onChange={(state) => setPluginConfig("markdownFlags", state)} />
</SettingsBoxItem>
)}
<SettingsBoxItem title="下拉菜单" description={["这是一个下拉菜单。", `当前状态为:${pluginConfig.dropdownConfigItem}`]}>
<Dropdown
items={[
["A" as const, "我是 A 选项"],
["B" as const, "我是 B 选项"],
["C" as const, "我是 C 选项"],
]}
selected={pluginConfig.dropdownConfigItem}
onChange={(state) => setPluginConfig("dropdownConfigItem", state)}
width="150px"
/>
</SettingsBoxItem>
<SettingsBoxItem title="输入框" description={["这是一个输入框。", `当前状态为:${pluginConfig.inputConfigItem}`]} isLast={true}>
<Input value={pluginConfig.inputConfigItem} onChange={(state) => setPluginConfig("inputConfigItem", state)} />
</SettingsBoxItem>
</SettingsBox>
</SettingsSection>
</>
Expand Down
1 change: 1 addition & 0 deletions src/utils/sep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { sep as s } from "path";
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"resolveJsonModule": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"esModuleInterop": true,
"noEmit": true
},
"include": ["src"],
Expand Down
Loading

0 comments on commit 71b988c

Please sign in to comment.