├── .gitignore
├── LICENSE
├── README.md
├── README.zh.md
├── bun.lock
├── example.svg
├── package.json
├── scripts
└── build
│ ├── index.ts
│ └── plugins.ts
├── src
├── components
│ └── index.tsx
├── icons
│ ├── mac
│ │ ├── close.svg
│ │ ├── minimize.svg
│ │ └── stretch.svg
│ └── windows
│ │ ├── close.svg
│ │ ├── minimize.svg
│ │ └── stretch.svg
├── index.tsx
├── styles
│ └── index.scss
└── util.ts
├── tsconfig.json
└── types
├── docsify.d.ts
└── index.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Yuki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docsify-chat
2 |
3 | A docsify plugin for generate chat panel from markdown
4 |
5 | Read this in other languages: English | [简体中文](./README.zh.md)
6 |
7 | ```markdown
8 |
9 |
10 | #### **Yuki**
11 |
12 | Hello
13 |
14 | #### **Robot**
15 |
16 | Ciallo ~(∠·ω< )⌒★
17 |
18 |
19 | ```
20 |
21 | 
22 |
23 | ## Installation
24 |
25 | 1. Add the docsify-chat plugin to your `index.html` after docsify.
26 |
27 | ```html
28 |
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | 2. Review the [Options](#options) section and configure as needed.
36 |
37 | ```javascript
38 | window.$docsify = {
39 | // ...
40 | chat: {
41 | // chat panel title
42 | title: "Dialog",
43 | // set avatar url
44 | users: [
45 | { nickname: "Yuki", avatar: "" },
46 | { nickname: "Robot", avatar: "" },
47 | ],
48 | },
49 | };
50 | ```
51 |
52 | ## Usage
53 |
54 | 1. Define a chat set using `chat:start` and `chat:end` HTML comments.
55 |
56 | HTML comments are used to mark the start and end of a chat set.
57 |
58 | ```markdown
59 |
60 |
61 | ...
62 |
63 |
64 | ```
65 |
66 | 2. Define chat within a message set using heading + bold markdown.
67 |
68 | Heading text will be used as the user nickname, and all proceeding content will be associated with that chat up to start of the next message or a `chat:end` comment.
69 |
70 | ```markdown
71 |
72 |
73 | #### **Yuki**
74 |
75 | hello
76 |
77 | #### **Robot**
78 |
79 | hello world
80 |
81 |
82 | ```
83 |
84 | 3. Generate and display your chat panel.
85 |
86 | If you do not specify a user avatar, the initials of the nickname will be displayed by default.
87 |
88 | 
89 |
90 | ## Options
91 |
92 | Options are set within the [`window.$docsify`](https://docsify.js.org/#/configuration) configuration under the `chat` key:
93 |
94 | ```html
95 |
112 | ```
113 |
114 | ### title
115 |
116 | - Type: `string`
117 | - Default: `'Dialog'`
118 |
119 | Sets the chat panel title.
120 |
121 | You can also set the title for each chat panel individually in ``.
122 |
123 | **Configuration**
124 |
125 | ```javascript
126 | window.$docsify = {
127 | // ...
128 | chat: {
129 | title: 'chat history',
130 | },
131 | };
132 | ```
133 |
134 | **Example**
135 |
136 | ```markdown
137 |
138 |
139 |
140 |
141 |
142 | ```
143 |
144 | ### users
145 |
146 | - Type: `array`
147 | - Default: `[]`
148 |
149 | Specify a nickname to match the user's avatar, support network URL.
150 |
151 | **Configuration**
152 |
153 | ```javascript
154 | window.$docsify = {
155 | // ...
156 | chat: {
157 | users: [
158 | { nickname: 'Yuki', avatar: 'images/yuki.png' },
159 | { nickname: 'Robot', avatar: 'images/robot.png' },
160 | ],
161 | },
162 | };
163 | ```
164 |
165 | ### self
166 |
167 | > Before v0.5.0, this attribute was named `"myself"`, but it has now been renamed 'self'.
168 |
169 | - Type: `string`
170 | - Default: `null`
171 |
172 | Set your own global nickname, the dialog will be displayed on the right side of the chat panel.
173 |
174 | You can also set the user for each chat panel individually in ``.
175 |
176 | **Configuration**
177 |
178 | ```javascript
179 | window.$docsify = {
180 | // ...
181 | chat: {
182 | self: 'Yuki',
183 | },
184 | };
185 | ```
186 |
187 | **Example**
188 |
189 | ```markdown
190 |
191 |
192 |
193 |
194 |
195 | ```
196 |
197 | ### animation
198 |
199 | - Type: `number`
200 | - Default: `50`
201 |
202 | Adjust the duration of the chat panel fade-in and fade-out animation.
203 |
204 | **Configuration**
205 |
206 | ```javascript
207 | window.$docsify = {
208 | // ...
209 | chat: {
210 | animation: 50,
211 | },
212 | };
213 | ```
214 |
215 | ### os
216 |
217 | - Type: `string`
218 | - Default: `null`
219 |
220 | Define the system style of the title bar, support `"mac"` and `"windows"`.
221 |
222 | If it is not set, it will be based on the current browser `navigator Platform` Automatic rendering.
223 |
224 | **Configuration**
225 |
226 | ```javascript
227 | window.$docsify = {
228 | // ...
229 | chat: {
230 | os: 'mac',
231 | },
232 | };
233 | ```
234 |
235 | ## Postscript
236 |
237 | Because I wrote a chatbot framework, I needed a chat panel for illustrate. before I took screenshots directly in the software, but it felt too troublesome. I was thinking why can't it be generated directly with markdown? I've been looking for a long time, but I can't find any similar plugins, so I made one myself.
238 |
239 | In order to save time, the syntax refers to [docsify-tabs](https://github.com/jhildenbiddle/docsify-tabs), which took only half a day to make. Although it basically meets daily use, there may be some unknown bugs.
240 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # docsify-chat
2 |
3 | 一个基于 docsify 的插件,可在 markdown 中生成聊天对话
4 |
5 | 使用其他语言阅读:[English](./README.md) | 简体中文
6 |
7 | ```markdown
8 |
9 |
10 | #### **Yuki**
11 |
12 | Hello
13 |
14 | #### **Robot**
15 |
16 | Ciallo ~(∠·ω< )⌒★
17 |
18 |
19 | ```
20 |
21 | 
22 |
23 | ## 安装
24 |
25 | 1. 在 `index.html` 中添加 docsify-chat,必须在 docsify 之后引入。
26 |
27 | ```html
28 |
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | 2. 可以在 [配置项](#配置项) 中根据自身需要来进行相关配置。
36 |
37 | ```javascript
38 | window.$docsify = {
39 | // ...
40 | chat: {
41 | // 聊天面板标题
42 | title: "窗口",
43 | // 设置头像
44 | users: [
45 | { nickname: "Yuki", avatar: "" },
46 | { nickname: "Robot", avatar: "" },
47 | ],
48 | },
49 | };
50 | ```
51 |
52 | ## 使用
53 |
54 | 1. 使用 `chat:start` 与 `chat:end` 的 HTML 注释来定义聊天面板。
55 |
56 | HTML 注释用于标记聊天面板的开始和结束。
57 |
58 | ```markdown
59 |
60 |
61 | ...
62 |
63 |
64 | ```
65 |
66 | 2. 使用标题 + 粗体标记在聊天面板中定义消息。
67 |
68 | 标题文本将被用作用户昵称,后续所有内容都将视为对话框内容,直到下一个标题或 `chat:end` 标记结束。
69 |
70 | ```markdown
71 |
72 |
73 | #### **Yuki**
74 |
75 | hello
76 |
77 | #### **Robot**
78 |
79 | hello world
80 |
81 |
82 | ```
83 |
84 | 3. 若上述步骤无误,页面将会生成并显示聊天面板。
85 |
86 | 如果未指定用户头像,则默认情况下将显示昵称的首字母。
87 |
88 | 
89 |
90 | ## 配置项
91 |
92 | 相关配置可以在 [`window.$docsify`](https://docsify.js.org/#/configuration) 下的 `chat` 字段中定义:
93 |
94 | ```html
95 |
112 | ```
113 |
114 | ### title
115 |
116 | - 类型: `string`
117 | - 默认: `'Dialog'`
118 |
119 | 设置聊天面板的全局标题。
120 |
121 | 你还可以在 `` 中分别为每个聊天面板单独设置标题。
122 |
123 | **配置**
124 |
125 | ```javascript
126 | window.$docsify = {
127 | // ...
128 | chat: {
129 | title: '聊天记录',
130 | },
131 | };
132 | ```
133 |
134 | **语法**
135 |
136 | ```markdown
137 |
138 |
139 |
140 |
141 |
142 | ```
143 |
144 | ### users
145 |
146 | - 类型: `array`
147 | - 默认: `[]`
148 |
149 | 设置用户的头像与昵称,支持网络地址。
150 |
151 | **配置**
152 |
153 | ```javascript
154 | window.$docsify = {
155 | // ...
156 | chat: {
157 | users: [
158 | { nickname: 'Yuki', avatar: 'images/yuki.png' },
159 | { nickname: 'Robot', avatar: 'images/robot.png' },
160 | ],
161 | },
162 | };
163 | ```
164 |
165 | ### self
166 |
167 | > 在 v0.5.0 以前,该属性名为 `"myself"`,现已更名为 `"self"`。
168 |
169 | - 类型: `string`
170 | - 默认: `null`
171 |
172 | 定义一个昵称,该用户的对话框将显示在聊天面板的右侧。
173 |
174 | 你还可以在 `` 中分别为每个聊天面板单独设置用户。
175 |
176 | **配置**
177 |
178 | ```javascript
179 | window.$docsify = {
180 | // ...
181 | chat: {
182 | self: 'Yuki',
183 | },
184 | };
185 | ```
186 |
187 | **语法**
188 |
189 | ```markdown
190 |
191 |
192 |
193 |
194 |
195 | ```
196 |
197 | ### animation
198 |
199 | - 类型: `number`
200 | - 默认: `50`
201 |
202 | 调整聊天对话框淡入淡出的动画时长。
203 |
204 | **设置**
205 |
206 | ```javascript
207 | window.$docsify = {
208 | // ...
209 | chat: {
210 | animation: 50,
211 | },
212 | };
213 | ```
214 |
215 | ### os
216 |
217 | - 类型: `string`
218 | - 默认: `null`
219 |
220 | 定义标题栏的系统风格,支持 `"mac"` 与 `"windows"`。
221 |
222 | 如果不设置,将会根据当前浏览器 `navigator.platform` 自动渲染。
223 |
224 | **设置**
225 |
226 | ```javascript
227 | window.$docsify = {
228 | // ...
229 | chat: {
230 | os: 'mac',
231 | },
232 | };
233 | ```
234 |
235 | ## 补充
236 |
237 | 因为我写了一个 QQ 机器人框架,所以需要一个聊天面板在文档中做相关演示。在这之前我是直接在 QQ 里截图后丢到文档的,但这样感觉太麻烦了。突发奇想为什么不能直接用 markdown 来生成咧?但是我找了很长时间,都没有类似的插件,所以就自己做了一个。
238 |
239 | 为了节省时间,相关语法完全参照 [docsify-tabs](https://github.com/jhildenbiddle/docsify-tabs),只花了半天时间就做好了。虽然它基本满足日常使用,但可能会存在一些未知的 bug。
240 |
--------------------------------------------------------------------------------
/bun.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1,
3 | "workspaces": {
4 | "": {
5 | "name": "docsify-chat",
6 | "devDependencies": {
7 | "@types/bun": "latest",
8 | "hanno": "^0.0.1",
9 | "sass": "latest",
10 | },
11 | "peerDependencies": {
12 | "typescript": "latest",
13 | },
14 | },
15 | },
16 | "packages": {
17 | "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
18 |
19 | "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
20 |
21 | "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="],
22 |
23 | "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="],
24 |
25 | "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="],
26 |
27 | "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="],
28 |
29 | "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="],
30 |
31 | "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="],
32 |
33 | "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="],
34 |
35 | "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="],
36 |
37 | "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="],
38 |
39 | "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="],
40 |
41 | "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="],
42 |
43 | "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
44 |
45 | "@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="],
46 |
47 | "@types/node": ["@types/node@22.10.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww=="],
48 |
49 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
50 |
51 | "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
52 |
53 | "bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="],
54 |
55 | "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
56 |
57 | "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
58 |
59 | "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
60 |
61 | "hanno": ["hanno@0.0.1", "", { "peerDependencies": { "typescript": "latest" } }, "sha512-8Qz+sLC+eWDf2VuL+L6//LyYSQO+cATMaf14BMbOum5nUybZr1ahyNhGp9dG7uHR2Cb7PclOAN7c07DTmvMzLg=="],
62 |
63 | "immutable": ["immutable@5.0.3", "", {}, "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw=="],
64 |
65 | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
66 |
67 | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
68 |
69 | "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
70 |
71 | "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
72 |
73 | "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
74 |
75 | "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
76 |
77 | "readdirp": ["readdirp@4.1.1", "", {}, "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="],
78 |
79 | "sass": ["sass@1.86.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw=="],
80 |
81 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
82 |
83 | "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
84 |
85 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
86 |
87 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docsify-chat",
3 | "version": "1.0.2",
4 | "description": "A docsify plugin for generate chat panel from markdown",
5 | "main": "lib/docsify-chat.js",
6 | "packageManager": "bun@1.2.9",
7 | "files": [
8 | "lib"
9 | ],
10 | "scripts": {
11 | "build": "NODE_ENV=production bun ./scripts/build/index.ts"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/xueelf/docsify-chat.git"
16 | },
17 | "keywords": [
18 | "docs",
19 | "docsify",
20 | "docsify.js",
21 | "documentation",
22 | "javascript",
23 | "js",
24 | "markdown",
25 | "md",
26 | "plugin",
27 | "chat"
28 | ],
29 | "author": "Yuki ",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/xueelf/docsify-chat/issues"
33 | },
34 | "homepage": "https://github.com/xueelf/docsify-chat#readme",
35 | "devDependencies": {
36 | "@types/bun": "latest",
37 | "hanno": "^0.0.1",
38 | "sass": "latest"
39 | },
40 | "peerDependencies": {
41 | "typescript": "latest"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/scripts/build/index.ts:
--------------------------------------------------------------------------------
1 | import { build, type BuildConfig } from 'bun';
2 | import { sassPlugin, svgPlugin } from './plugins';
3 |
4 | const buildConfig: BuildConfig = {
5 | entrypoints: ['src/index.tsx'],
6 | outdir: 'lib',
7 | plugins: [sassPlugin(), svgPlugin()],
8 | };
9 |
10 | await build({
11 | ...buildConfig,
12 | naming: 'docsify-chat.[ext]',
13 | });
14 | await build({
15 | ...buildConfig,
16 | minify: true,
17 | naming: 'docsify-chat.min.[ext]',
18 | });
19 |
--------------------------------------------------------------------------------
/scripts/build/plugins.ts:
--------------------------------------------------------------------------------
1 | import type { BunPlugin, PluginBuilder } from 'bun';
2 | import { compileAsync } from 'sass';
3 |
4 | export function sassPlugin(): BunPlugin {
5 | return {
6 | name: 'sass',
7 | setup(builder: PluginBuilder) {
8 | builder.onLoad({ filter: /\.scss$/ }, async ({ path }) => {
9 | const { css } = await compileAsync(path, {
10 | style: builder.config.minify ? 'compressed' : 'expanded',
11 | });
12 |
13 | return {
14 | loader: 'text',
15 | contents: css,
16 | };
17 | });
18 | },
19 | };
20 | }
21 |
22 | export function svgPlugin(): BunPlugin {
23 | return {
24 | name: 'svg',
25 | setup(builder: PluginBuilder) {
26 | builder.onLoad({ filter: /\.svg$/ }, async ({ path }) => {
27 | let contents = await Bun.file(path).text();
28 |
29 | if (builder.config.minify) {
30 | contents = contents.replace(/\n(\s{2})*/g, '');
31 | }
32 | return {
33 | loader: 'text',
34 | contents,
35 | };
36 | });
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | import { stringToColor } from '@/util';
2 | import macClose from '@/icons//mac/close.svg';
3 | import macMinimize from '@/icons/mac/minimize.svg';
4 | import macStretch from '@/icons/mac/stretch.svg';
5 | import windowsClose from '@/icons/windows/close.svg';
6 | import windowsMinimize from '@/icons/windows/minimize.svg';
7 | import windowsStretch from '@/icons/windows/stretch.svg';
8 |
9 | function MacControls() {
10 | return (
11 | <>
12 |
13 |
14 |
15 | >
16 | );
17 | }
18 |
19 | function WindowsControls() {
20 | return (
21 | <>
22 |
23 |
24 |
25 | >
26 | );
27 | }
28 |
29 | function Controls() {
30 | const os = window.$docsify?.chat?.os;
31 |
32 | switch (os) {
33 | case 'mac':
34 | return ;
35 | case 'windows':
36 | return ;
37 | default:
38 | console.error(`os "${os}" is invalid argument`);
39 | }
40 | }
41 |
42 | export function TitleBar(props: { title: string }) {
43 | const os = window.$docsify?.chat?.os!;
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | {props.title}
51 |
52 | );
53 | }
54 |
55 | export function Avatar(props: { user: User }) {
56 | const { avatar, nickname } = props.user;
57 |
58 | if (avatar) {
59 | return (
60 |
61 |

62 |
63 | );
64 | } else {
65 | const color = stringToColor(nickname);
66 | const first_char = nickname.substring(0, 1);
67 |
68 | return (
69 |
75 | {first_char}
76 |
77 | );
78 | }
79 | }
80 |
81 | export function Message(props: { user: User; content: string; self: boolean }) {
82 | const { user, content, self } = props;
83 |
84 | return (
85 |
86 | {!self &&
}
87 |
88 |
{user.nickname}
89 |
{content}
90 |
91 | {self &&
}
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/src/icons/mac/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/icons/mac/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/icons/mac/stretch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/icons/windows/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/icons/windows/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/icons/windows/stretch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import styleContent from '@/styles/index.scss';
2 | import { version } from 'package.json';
3 | import { Message, TitleBar } from '@/components';
4 |
5 | const styleElement = document.createElement('style');
6 | styleElement.textContent = styleContent;
7 | document.head.appendChild(styleElement);
8 |
9 | enum ClassName {
10 | ChatImage = 'chat-image',
11 | ChatMessage = 'chat-message',
12 | ChatPanel = 'chat-panel',
13 | }
14 |
15 | const IS_MAC = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
16 | const CHAT_PANEL_MARKUP =
17 | /( *)()(?:(?!())[\s\S])*()/;
18 | const CHAT_TITLE_MARKUP = /[\r\n]*(\s*)/;
19 | const CHAT_SELF_MARKUP = /[\r\n]*(\s*)/;
20 | const CHAT_MESSAGE_MARKUP =
21 | /[\r\n]*(\s*)#{1,6}\s*[*_]{2}\s*(.*[^\s])\s*[*_]{2}[\r\n]+([\s\S]*?)(?=#{1,6}\s*[*_]{2}|)/m;
22 |
23 | const setting: DocsifyChatSetting = {
24 | animation: 50,
25 | myself: null,
26 | self: null,
27 | os: IS_MAC ? 'mac' : 'windows',
28 | title: 'Dialog',
29 | users: [],
30 | version,
31 | };
32 |
33 | /**
34 | * 解析消息结构体
35 | *
36 | * @param markdown - 原始 Markdown 文本
37 | * @returns HTML 结构体
38 | */
39 | function parseContent(markdown: string): string {
40 | const regex = /!\[(.*?)\]\((.*?)\)/;
41 | const segments = markdown.trim().split('\n');
42 | const content = segments.map(segment => {
43 | const is_image = regex.test(segment);
44 |
45 | if (is_image) {
46 | const image = '
';
47 | return segment.replace(regex, image);
48 | } else {
49 | const text = `${segment}
`;
50 | return segment.replace(segment, text);
51 | }
52 | });
53 |
54 | return content.join('');
55 | }
56 |
57 | function renderChat(content: string, vm: Docsify) {
58 | let chatExecs;
59 | let messageExecs;
60 |
61 | while ((chatExecs = CHAT_PANEL_MARKUP.exec(content))) {
62 | let raw_chat = chatExecs[0];
63 | let title = setting.title;
64 | let self = setting.self || setting.myself;
65 | let chat_start_replacement = '';
66 | let chat_end_replacement = '';
67 |
68 | const has_title = CHAT_TITLE_MARKUP.test(raw_chat);
69 | const has_self = CHAT_SELF_MARKUP.test(raw_chat);
70 | const has_message = CHAT_MESSAGE_MARKUP.test(raw_chat);
71 |
72 | if (has_title) {
73 | const titleExecs = CHAT_TITLE_MARKUP.exec(raw_chat)!;
74 |
75 | title = titleExecs[2];
76 | raw_chat = raw_chat.replace(CHAT_TITLE_MARKUP, '');
77 | }
78 | const chat_title_bar = ;
79 |
80 | if (has_self) {
81 | const selfExecs = CHAT_SELF_MARKUP.exec(raw_chat)!;
82 |
83 | self = selfExecs[2];
84 | raw_chat = raw_chat.replace(CHAT_SELF_MARKUP, '');
85 | }
86 |
87 | if (has_message) {
88 | chat_start_replacement = `${chat_title_bar}`;
89 | chat_end_replacement = ``;
90 |
91 | while ((messageExecs = CHAT_MESSAGE_MARKUP.exec(raw_chat))) {
92 | const nickname = messageExecs[2];
93 | const content = parseContent(messageExecs[3]);
94 | const user = setting.users.find(item => item.nickname === nickname) ?? {
95 | nickname,
96 | };
97 | const is_me = self === nickname;
98 |
99 | raw_chat = raw_chat.replace(
100 | messageExecs[0],
101 | ,
102 | );
103 | }
104 | }
105 | const chat_start = chatExecs[2];
106 | const chat_end = chatExecs[4];
107 |
108 | raw_chat = raw_chat.replace(chat_start, chat_start_replacement);
109 | raw_chat = raw_chat.replace(chat_end, chat_end_replacement);
110 | raw_chat = raw_chat.replace(/(\s{2,}|\n)/g, '');
111 | content = content.replace(chatExecs[0], raw_chat);
112 | }
113 | return content;
114 | }
115 |
116 | function createResizeObserver() {
117 | return new ResizeObserver(entries => {
118 | entries.forEach(entry => {
119 | const { target } = entry;
120 | const { offsetWidth } = target as HTMLDivElement;
121 | const chatImageElements = target.getElementsByClassName(ClassName.ChatImage);
122 |
123 | for (let i = 0; i < chatImageElements.length; i++) {
124 | const element = chatImageElements[i] as HTMLDivElement;
125 | element.style.maxWidth = `calc((${offsetWidth}px - 5rem) / 2)`;
126 | }
127 | });
128 | });
129 | }
130 |
131 | function createIntersectionObserver() {
132 | return new IntersectionObserver(entries => {
133 | entries.forEach(entry => {
134 | const { target, isIntersecting } = entry;
135 | const chatMessageElements = target.getElementsByClassName(ClassName.ChatMessage);
136 |
137 | for (let i = 0; i < chatMessageElements.length; i++) {
138 | const element = chatMessageElements[i];
139 |
140 | if (isIntersecting) {
141 | setTimeout(() => element.classList.add('show'), i * setting.animation);
142 | } else {
143 | // TODO: /人◕ ‿‿ ◕人\ clearTimeout
144 | element.classList.remove('show');
145 | }
146 | }
147 | });
148 | });
149 | }
150 |
151 | function docsifyChat(hook: DocsifyHook, vm: Docsify) {
152 | let has_chat: boolean;
153 | const resizeObserver = createResizeObserver();
154 | const intersectionObserver = createIntersectionObserver();
155 |
156 | hook.beforeEach(content => {
157 | has_chat = CHAT_PANEL_MARKUP.test(content);
158 |
159 | if (has_chat) {
160 | content = renderChat(content, vm);
161 | }
162 | return content;
163 | });
164 | hook.doneEach(() => {
165 | if (setting.myself) {
166 | console.warn(
167 | 'The "myself" attribute is about to be abandoned, it is recommended to replace it with "self".',
168 | );
169 | }
170 | resizeObserver.disconnect();
171 | intersectionObserver.disconnect();
172 |
173 | if (!has_chat) {
174 | return;
175 | }
176 | const chatPanelElements = document.getElementsByClassName(ClassName.ChatPanel);
177 |
178 | for (let i = 0; i < chatPanelElements.length; i++) {
179 | const element = chatPanelElements[i];
180 |
181 | resizeObserver.observe(element);
182 | intersectionObserver.observe(element);
183 | }
184 | });
185 | }
186 |
187 | window.$docsify ??= {};
188 | window.$docsify.chat ??= {};
189 | window.$docsify.plugins ??= [];
190 | window.$docsify.plugins.push(docsifyChat);
191 | Object.assign(setting, window.$docsify.chat);
192 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 768px) {
2 | .controls {
3 | display: none !important;
4 | }
5 | }
6 |
7 | .chat-panel {
8 | position: relative;
9 | border-radius: 0.5rem;
10 | margin: 1rem auto;
11 | background-color: rgb(246, 248, 250);
12 | overflow: hidden;
13 |
14 | button {
15 | border: 0;
16 | background: none;
17 | margin: 0;
18 | padding: 0;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | }
23 |
24 | .title-bar {
25 | text-align: center;
26 |
27 | &.mac {
28 | display: flex;
29 | justify-content: center;
30 | padding: 0.9rem 1rem;
31 | width: 100%;
32 | // TODO: border
33 | // border-bottom: solid 0.9px hsla(var(--color), 0.3);
34 |
35 | .title {
36 | font-weight: 500;
37 | font-size: 0.9rem;
38 | line-height: 0.9rem;
39 | letter-spacing: 0.5px;
40 | }
41 |
42 | .controls {
43 | position: absolute;
44 | top: 1rem;
45 | left: 1rem;
46 | display: grid;
47 | gap: 0.6rem;
48 | grid-template-columns: repeat(3, 0.8rem);
49 |
50 | svg {
51 | opacity: 0;
52 | }
53 |
54 | &:hover button {
55 | transform: scale(1.2);
56 | }
57 |
58 | &:hover svg {
59 | opacity: 1;
60 | }
61 |
62 | .close {
63 | --bg-color: #ff5f56;
64 | --border-color: #e0443e;
65 | }
66 |
67 | .stretch {
68 | --bg-color: #27c93f;
69 | --border-color: #1aab29;
70 | }
71 |
72 | .stretch svg {
73 | transform: rotate(90deg);
74 | }
75 |
76 | .minimize {
77 | --bg-color: #ffbd2e;
78 | --border-color: #dea123;
79 | }
80 |
81 | .circle {
82 | width: 0.8rem;
83 | height: 0.8rem;
84 | border-radius: 50%;
85 | background-color: var(--bg-color);
86 | box-shadow: 0 0 0 0.5px var(--border-color);
87 | transition: transform 0.1s ease-in;
88 | }
89 | }
90 | }
91 |
92 | &.windows {
93 | display: flex;
94 | flex-shrink: 0;
95 | width: 100%;
96 | height: 28px;
97 | align-items: center;
98 | justify-content: center;
99 | position: relative;
100 | border-radius: 6px 6px 0 0;
101 |
102 | .title {
103 | font-size: 0.8rem;
104 | }
105 |
106 | .controls {
107 | height: 100%;
108 | position: absolute;
109 | right: 0;
110 | display: flex;
111 | align-items: center;
112 |
113 | svg {
114 | width: 12px;
115 | height: 100%;
116 | }
117 |
118 | button {
119 | height: 100%;
120 | padding: 0 18px;
121 | transition: all ease-in-out 60ms;
122 | }
123 |
124 | button:hover {
125 | background: rgba(136, 136, 136, 0.2);
126 | }
127 |
128 | button[class='close']:hover {
129 | background: rgba(255, 0, 0, 0.8);
130 | }
131 |
132 | button[class='close']:hover svg {
133 | filter: invert(1);
134 | }
135 | }
136 | }
137 | }
138 |
139 | .main-area {
140 | width: 100%;
141 | min-height: auto;
142 |
143 | .chat-message {
144 | display: flex;
145 | position: relative;
146 | padding: 1rem;
147 | opacity: 0;
148 | transform: translate(-10%);
149 | transition: transform 0.4s ease-out, opacity 0.4s ease-in;
150 |
151 | &.self {
152 | transform: translate(10%);
153 | justify-content: flex-end;
154 |
155 | .message-box {
156 | margin-left: 0;
157 | margin-right: 0.5rem;
158 | }
159 |
160 | .nickname {
161 | text-align: right;
162 | }
163 | }
164 |
165 | &.show {
166 | opacity: 1;
167 | transform: translate(0);
168 | }
169 |
170 | .avatar {
171 | width: 2.5rem;
172 | height: 2.5rem;
173 | overflow: hidden;
174 | flex-shrink: 0;
175 | border-radius: 50%;
176 | line-height: 2.5rem;
177 | color: #fff;
178 | text-align: center;
179 |
180 | img {
181 | display: inline-flex;
182 | line-height: 0;
183 | justify-content: center;
184 | align-items: center;
185 | color: #fff;
186 | }
187 | }
188 |
189 | .message-box {
190 | display: inline-block;
191 | margin-left: 0.5rem;
192 | max-width: 90%;
193 | vertical-align: top;
194 |
195 | .nickname {
196 | font-size: 0.8rem;
197 | color: gray;
198 | }
199 |
200 | .message {
201 | position: relative;
202 | width: fit-content;
203 | font-size: 0.9rem;
204 | border-radius: 0.5rem;
205 | background-color: #fff;
206 | word-break: break-all;
207 | padding: 0.6rem 0.7rem;
208 | margin-top: 0.2rem;
209 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px,
210 | rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
211 |
212 | .chat-text {
213 | min-height: 1rem;
214 | }
215 |
216 | .chat-image {
217 | display: block;
218 | min-width: 5rem;
219 | border-radius: 0.3rem;
220 | margin-bottom: 0.3rem;
221 | }
222 | }
223 | }
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 字符串转换 color 十六进制
3 | *
4 | * @param string - 字符
5 | * @returns 十六进制
6 | */
7 | export function stringToColor(string: string) {
8 | let hash = 0;
9 | let color = '#';
10 |
11 | for (let i = 0; i < string.length; i++) {
12 | hash = string.charCodeAt(i) + ((hash << 5) - hash);
13 | }
14 | for (let i = 0; i < 3; i++) {
15 | const value = (hash >> (i * 8)) & 0xff;
16 | color += ('00' + value.toString(16)).substr(-2);
17 | }
18 | return color;
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Language and Environment */
4 | "target": "ESNext",
5 | "lib": ["ESNext", "DOM"],
6 | "jsx": "react-jsx",
7 | "jsxImportSource": "hanno",
8 | "moduleDetection": "force",
9 |
10 | /* Modules */
11 | "module": "ESNext",
12 | "moduleResolution": "bundler",
13 | "baseUrl": "./",
14 | "paths": {
15 | "@/*": ["src/*"]
16 | },
17 | "allowImportingTsExtensions": true,
18 |
19 | /* Emit */
20 | "noEmit": true,
21 |
22 | /* Interop Constraints */
23 | "verbatimModuleSyntax": true,
24 | "esModuleInterop": true,
25 | "forceConsistentCasingInFileNames": true,
26 |
27 | /* Type Checking */
28 | "strict": true,
29 | "noFallthroughCasesInSwitch": true,
30 |
31 | /* Completeness */
32 | "skipLibCheck": true
33 | },
34 | "include": ["scripts", "src", "types"]
35 | }
36 |
--------------------------------------------------------------------------------
/types/docsify.d.ts:
--------------------------------------------------------------------------------
1 | type Hook = T extends unknown[]
2 | ? (callback: (...args: T) => void) => void
3 | : (callback: () => void) => void;
4 |
5 | interface Docsify {}
6 |
7 | interface DocsifyHook {
8 | /** 初始化完成后调用,只调用一次,没有参数。 */
9 | init: Hook;
10 | /** 每次开始解析 Markdown 内容时调用。 */
11 | beforeEach: Hook<[string]>;
12 | /** 解析成 html 后调用。 */
13 | afterEach: Hook<[string, (html: string) => void]>;
14 | /** 每次路由切换时数据全部加载完成后调用,没有参数。 */
15 | doneEach: Hook;
16 | /** 初始化并第一次加载完成数据后调用,只触发一次,没有参数。 */
17 | mounted: Hook;
18 | /** 初始化并第一次加载完成数据后调用,没有参数。 */
19 | ready: Hook;
20 | }
21 |
22 | interface User {
23 | avatar?: string;
24 | nickname: string;
25 | }
26 |
27 | interface DocsifyChatSetting {
28 | animation: number;
29 | myself: string | null;
30 | self: string | null;
31 | os: 'mac' | 'windows';
32 | title: string;
33 | users: User[];
34 | version: string;
35 | }
36 |
37 | interface Window {
38 | $docsify?: {
39 | chat?: Partial;
40 | plugins?: Function[];
41 | };
42 | // Docsify: Docsify;
43 | }
44 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const content: string;
3 | export default content;
4 | }
5 |
6 | declare module '*.scss' {
7 | const content: string;
8 | export default content;
9 | }
10 |
--------------------------------------------------------------------------------