markdown-to-mfuns-json 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,226 @@
1
+ /**
2
+ * 喵御宅(Mfuns)图片上传模块
3
+ */
4
+ const MFUNS_API_BASE = 'https://api.mfuns.net';
5
+ const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
6
+ const CONCURRENT_UPLOADS = 3; // 并发数
7
+ /** 支持的图片 MIME 类型 */
8
+ const SUPPORTED_IMAGE_TYPES = [
9
+ 'image/jpeg',
10
+ 'image/png',
11
+ 'image/gif',
12
+ 'image/webp',
13
+ 'image/bmp',
14
+ 'image/svg+xml',
15
+ ];
16
+ /** 支持的图片扩展名(用于检测 URL) */
17
+ const SUPPORTED_IMAGE_EXTS = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i;
18
+ /**
19
+ * 检测是否为图片 URL
20
+ */
21
+ function isImageUrl(url) {
22
+ return SUPPORTED_IMAGE_EXTS.test(url);
23
+ }
24
+ /**
25
+ * 检测是否为 base64 图片
26
+ */
27
+ function isBase64Image(url) {
28
+ return /^data:image\/(png|jpe?g|gif|webp|bmp|svg\+xml);base64,/i.test(url);
29
+ }
30
+ /**
31
+ * 从 base64 创建 File 对象
32
+ */
33
+ function base64ToFile(base64, filename) {
34
+ const match = base64.match(/^data:(image\/\w+(?:\+xml)?);base64,(.+)$/);
35
+ if (!match) {
36
+ throw new Error('Invalid base64 image format');
37
+ }
38
+ const mimeType = match[1];
39
+ const base64Data = match[2];
40
+ const byteString = atob(base64Data);
41
+ const ab = new ArrayBuffer(byteString.length);
42
+ const ia = new Uint8Array(ab);
43
+ for (let i = 0; i < byteString.length; i++) {
44
+ ia[i] = byteString.charCodeAt(i);
45
+ }
46
+ const blob = new Blob([ab], { type: mimeType });
47
+ const ext = mimeType.replace('image/', '').replace('+xml', '');
48
+ const name = filename || `image.${ext}`;
49
+ return new File([blob], name, { type: mimeType });
50
+ }
51
+ /**
52
+ * 下载网络图片
53
+ */
54
+ async function downloadImage(url) {
55
+ const response = await fetch(url);
56
+ if (!response.ok) {
57
+ throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
58
+ }
59
+ const contentType = response.headers.get('content-type');
60
+ if (contentType && !SUPPORTED_IMAGE_TYPES.some(t => contentType.includes(t))) {
61
+ throw new Error(`Unsupported image type: ${contentType}`);
62
+ }
63
+ const blob = await response.blob();
64
+ if (blob.size > MAX_IMAGE_SIZE) {
65
+ throw new Error(`Image size exceeds 20MB limit: ${(blob.size / 1024 / 1024).toFixed(2)}MB`);
66
+ }
67
+ // 从 URL 提取文件名
68
+ const urlObj = new URL(url);
69
+ const pathname = urlObj.pathname;
70
+ const filename = pathname.substring(pathname.lastIndexOf('/') + 1) || 'image.jpg';
71
+ return new File([blob], filename, { type: blob.type || 'image/jpeg' });
72
+ }
73
+ /**
74
+ * 上传单个图片到喵御宅服务器
75
+ */
76
+ export async function uploadImageToMfuns(file, options) {
77
+ if (file.size > MAX_IMAGE_SIZE) {
78
+ throw new Error(`Image size exceeds 20MB limit: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
79
+ }
80
+ const formData = new FormData();
81
+ formData.append('file', file);
82
+ const headers = {
83
+ Authorization: options.token,
84
+ };
85
+ if (options.deviceInfo) {
86
+ const { clientType, deviceId, osName, osVersion, deviceModel } = options.deviceInfo;
87
+ if (clientType)
88
+ headers['X-Client-Type'] = clientType;
89
+ if (deviceId)
90
+ headers['X-Device-Id'] = deviceId;
91
+ if (osName)
92
+ headers['X-OS-Name'] = osName;
93
+ if (osVersion)
94
+ headers['X-OS-Version'] = osVersion;
95
+ if (deviceModel)
96
+ headers['X-Device-Model'] = deviceModel;
97
+ }
98
+ const response = await fetch(`${MFUNS_API_BASE}/v1/media/upload_image`, {
99
+ method: 'POST',
100
+ headers,
101
+ body: formData,
102
+ });
103
+ if (!response.ok) {
104
+ throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
105
+ }
106
+ const result = await response.json();
107
+ if (result.code !== 1) {
108
+ throw new Error(`Upload failed: ${result.msg || 'Unknown error'}`);
109
+ }
110
+ return result.data.file.file_path;
111
+ }
112
+ /**
113
+ * 处理图片 URL(下载/转换 + 上传)
114
+ */
115
+ export async function processImageUrl(url, options) {
116
+ // 没有 token,检查是否需要上传
117
+ if (!options?.token) {
118
+ if (isBase64Image(url)) {
119
+ throw new Error('Base64 images must be uploaded to Mfuns server. ' +
120
+ 'Please provide a token in options.');
121
+ }
122
+ // 网络图片保留原 URL
123
+ return url;
124
+ }
125
+ // 有 token,处理上传
126
+ let file;
127
+ if (isBase64Image(url)) {
128
+ file = base64ToFile(url);
129
+ }
130
+ else if (isImageUrl(url)) {
131
+ file = await downloadImage(url);
132
+ }
133
+ else {
134
+ // 不是图片 URL,原样返回
135
+ return url;
136
+ }
137
+ return uploadImageToMfuns(file, options);
138
+ }
139
+ /**
140
+ * 并发控制处理器
141
+ */
142
+ export class ConcurrentProcessor {
143
+ constructor(processor, concurrency) {
144
+ this.processor = processor;
145
+ this.concurrency = concurrency;
146
+ this.queue = [];
147
+ this.running = 0;
148
+ this.results = new Map();
149
+ }
150
+ async process(items) {
151
+ return new Promise((resolve, reject) => {
152
+ let completed = 0;
153
+ let hasError = false;
154
+ const checkComplete = () => {
155
+ if (hasError)
156
+ return;
157
+ if (completed === items.length) {
158
+ const ordered = items.map((_, i) => this.results.get(i));
159
+ resolve(ordered);
160
+ }
161
+ };
162
+ const runNext = () => {
163
+ if (hasError)
164
+ return;
165
+ while (this.running < this.concurrency && this.queue.length > 0) {
166
+ const task = this.queue.shift();
167
+ this.running++;
168
+ // 找到这个 task 对应的 index
169
+ const index = items.indexOf(task.item);
170
+ this.processor(task.item, index)
171
+ .then((result) => {
172
+ if (hasError)
173
+ return;
174
+ this.results.set(index, result);
175
+ task.resolve(result);
176
+ })
177
+ .catch((error) => {
178
+ if (hasError)
179
+ return;
180
+ hasError = true;
181
+ task.reject(error);
182
+ reject(error);
183
+ })
184
+ .finally(() => {
185
+ this.running--;
186
+ completed++;
187
+ checkComplete();
188
+ runNext();
189
+ });
190
+ }
191
+ };
192
+ // 将任务加入队列
193
+ items.forEach((item) => {
194
+ new Promise((res, rej) => {
195
+ this.queue.push({ item, resolve: res, reject: rej });
196
+ });
197
+ });
198
+ runNext();
199
+ });
200
+ }
201
+ }
202
+ /**
203
+ * 批量处理图片 URL
204
+ */
205
+ export async function processImageUrls(urls, options, onError) {
206
+ const processor = new ConcurrentProcessor(async (url) => {
207
+ try {
208
+ return await processImageUrl(url, options);
209
+ }
210
+ catch (error) {
211
+ if (onError) {
212
+ const action = onError(error, url);
213
+ if (action === 'skip') {
214
+ return '';
215
+ }
216
+ else if (action === 'keep-original') {
217
+ return url;
218
+ }
219
+ }
220
+ throw error;
221
+ }
222
+ }, CONCURRENT_UPLOADS);
223
+ return processor.process(urls);
224
+ }
225
+
226
+ module.exports = { isImageUrl, isBase64Image, base64ToFile, downloadImage, uploadImageToMfuns, processImageUrl, processImageUrls };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 喵御宅(Mfuns)图片上传模块
3
+ */
4
+ /** 设备信息 */
5
+ export interface DeviceInfo {
6
+ clientType?: string;
7
+ deviceId?: string;
8
+ osName?: string;
9
+ osVersion?: string;
10
+ deviceModel?: string;
11
+ }
12
+ /** 上传配置 */
13
+ export interface UploadOptions {
14
+ /** Redis session token(必填,否则无法上传) */
15
+ token: string;
16
+ /** 可选设备信息 */
17
+ deviceInfo?: DeviceInfo;
18
+ }
19
+ /**
20
+ * 检测是否为图片 URL
21
+ */
22
+ export declare function isImageUrl(url: string): boolean;
23
+ /**
24
+ * 检测是否为 base64 图片
25
+ */
26
+ export declare function isBase64Image(url: string): boolean;
27
+ /**
28
+ * 上传单个图片到喵御宅服务器
29
+ */
30
+ export declare function uploadImageToMfuns(file: File, options: UploadOptions): Promise<string>;
31
+ /**
32
+ * 处理图片 URL(下载/转换 + 上传)
33
+ */
34
+ export declare function processImageUrl(url: string, options?: UploadOptions): Promise<string>;
35
+ /**
36
+ * 并发控制处理器
37
+ */
38
+ export declare class ConcurrentProcessor<T, R> {
39
+ private processor;
40
+ private concurrency;
41
+ private queue;
42
+ private running;
43
+ private results;
44
+ constructor(processor: (item: T, index: number) => Promise<R>, concurrency: number);
45
+ process(items: T[]): Promise<R[]>;
46
+ }
47
+ /**
48
+ * 批量处理图片 URL
49
+ */
50
+ export declare function processImageUrls(urls: string[], options?: UploadOptions, onError?: (error: Error, url: string) => 'throw' | 'skip' | 'keep-original'): Promise<string[]>;
51
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 喵御宅(Mfuns)图片上传模块
3
+ */
4
+ /** 设备信息 */
5
+ export interface DeviceInfo {
6
+ clientType?: string;
7
+ deviceId?: string;
8
+ osName?: string;
9
+ osVersion?: string;
10
+ deviceModel?: string;
11
+ }
12
+ /** 上传配置 */
13
+ export interface UploadOptions {
14
+ /** Redis session token(必填,否则无法上传) */
15
+ token: string;
16
+ /** 可选设备信息 */
17
+ deviceInfo?: DeviceInfo;
18
+ }
19
+ /**
20
+ * 检测是否为图片 URL
21
+ */
22
+ export declare function isImageUrl(url: string): boolean;
23
+ /**
24
+ * 检测是否为 base64 图片
25
+ */
26
+ export declare function isBase64Image(url: string): boolean;
27
+ /**
28
+ * 上传单个图片到喵御宅服务器
29
+ */
30
+ export declare function uploadImageToMfuns(file: File, options: UploadOptions): Promise<string>;
31
+ /**
32
+ * 处理图片 URL(下载/转换 + 上传)
33
+ */
34
+ export declare function processImageUrl(url: string, options?: UploadOptions): Promise<string>;
35
+ /**
36
+ * 并发控制处理器
37
+ */
38
+ export declare class ConcurrentProcessor<T, R> {
39
+ private processor;
40
+ private concurrency;
41
+ private queue;
42
+ private running;
43
+ private results;
44
+ constructor(processor: (item: T, index: number) => Promise<R>, concurrency: number);
45
+ process(items: T[]): Promise<R[]>;
46
+ }
47
+ /**
48
+ * 批量处理图片 URL
49
+ */
50
+ export declare function processImageUrls(urls: string[], options?: UploadOptions, onError?: (error: Error, url: string) => 'throw' | 'skip' | 'keep-original'): Promise<string[]>;
51
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,WAAW;AACX,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,WAAW;AACX,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAeD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD;AAwDD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;GAEG;AACH,qBAAa,mBAAmB,CAAC,CAAC,EAAE,CAAC;IAMjC,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,WAAW;IANrB,OAAO,CAAC,KAAK,CAAmF;IAChG,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAwB;gBAG7B,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACjD,WAAW,EAAE,MAAM;IAGvB,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAsDxC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,GAAG,eAAe,GAC1E,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * 喵御宅(Mfuns)图片上传模块
3
+ */
4
+ const MFUNS_API_BASE = 'https://api.mfuns.net';
5
+ const MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB
6
+ const CONCURRENT_UPLOADS = 3; // 并发数
7
+ /** 支持的图片 MIME 类型 */
8
+ const SUPPORTED_IMAGE_TYPES = [
9
+ 'image/jpeg',
10
+ 'image/png',
11
+ 'image/gif',
12
+ 'image/webp',
13
+ 'image/bmp',
14
+ 'image/svg+xml',
15
+ ];
16
+ /** 支持的图片扩展名(用于检测 URL) */
17
+ const SUPPORTED_IMAGE_EXTS = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i;
18
+ /**
19
+ * 检测是否为图片 URL
20
+ */
21
+ export function isImageUrl(url) {
22
+ return SUPPORTED_IMAGE_EXTS.test(url);
23
+ }
24
+ /**
25
+ * 检测是否为 base64 图片
26
+ */
27
+ export function isBase64Image(url) {
28
+ return /^data:image\/(png|jpe?g|gif|webp|bmp|svg\+xml);base64,/i.test(url);
29
+ }
30
+ /**
31
+ * 从 base64 创建 File 对象
32
+ */
33
+ function base64ToFile(base64, filename) {
34
+ const match = base64.match(/^data:(image\/\w+(?:\+xml)?);base64,(.+)$/);
35
+ if (!match) {
36
+ throw new Error('Invalid base64 image format');
37
+ }
38
+ const mimeType = match[1];
39
+ const base64Data = match[2];
40
+ const byteString = atob(base64Data);
41
+ const ab = new ArrayBuffer(byteString.length);
42
+ const ia = new Uint8Array(ab);
43
+ for (let i = 0; i < byteString.length; i++) {
44
+ ia[i] = byteString.charCodeAt(i);
45
+ }
46
+ const blob = new Blob([ab], { type: mimeType });
47
+ const ext = mimeType.replace('image/', '').replace('+xml', '');
48
+ const name = filename || `image.${ext}`;
49
+ return new File([blob], name, { type: mimeType });
50
+ }
51
+ /**
52
+ * 下载网络图片
53
+ */
54
+ async function downloadImage(url) {
55
+ const response = await fetch(url);
56
+ if (!response.ok) {
57
+ throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
58
+ }
59
+ const contentType = response.headers.get('content-type');
60
+ if (contentType && !SUPPORTED_IMAGE_TYPES.some(t => contentType.includes(t))) {
61
+ throw new Error(`Unsupported image type: ${contentType}`);
62
+ }
63
+ const blob = await response.blob();
64
+ if (blob.size > MAX_IMAGE_SIZE) {
65
+ throw new Error(`Image size exceeds 20MB limit: ${(blob.size / 1024 / 1024).toFixed(2)}MB`);
66
+ }
67
+ // 从 URL 提取文件名
68
+ const urlObj = new URL(url);
69
+ const pathname = urlObj.pathname;
70
+ const filename = pathname.substring(pathname.lastIndexOf('/') + 1) || 'image.jpg';
71
+ return new File([blob], filename, { type: blob.type || 'image/jpeg' });
72
+ }
73
+ /**
74
+ * 上传单个图片到喵御宅服务器
75
+ */
76
+ export async function uploadImageToMfuns(file, options) {
77
+ if (file.size > MAX_IMAGE_SIZE) {
78
+ throw new Error(`Image size exceeds 20MB limit: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
79
+ }
80
+ const formData = new FormData();
81
+ formData.append('file', file);
82
+ const headers = {
83
+ Authorization: options.token,
84
+ };
85
+ if (options.deviceInfo) {
86
+ const { clientType, deviceId, osName, osVersion, deviceModel } = options.deviceInfo;
87
+ if (clientType)
88
+ headers['X-Client-Type'] = clientType;
89
+ if (deviceId)
90
+ headers['X-Device-Id'] = deviceId;
91
+ if (osName)
92
+ headers['X-OS-Name'] = osName;
93
+ if (osVersion)
94
+ headers['X-OS-Version'] = osVersion;
95
+ if (deviceModel)
96
+ headers['X-Device-Model'] = deviceModel;
97
+ }
98
+ const response = await fetch(`${MFUNS_API_BASE}/v1/media/upload_image`, {
99
+ method: 'POST',
100
+ headers,
101
+ body: formData,
102
+ });
103
+ if (!response.ok) {
104
+ throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
105
+ }
106
+ const result = await response.json();
107
+ if (result.code !== 1) {
108
+ throw new Error(`Upload failed: ${result.msg || 'Unknown error'}`);
109
+ }
110
+ return result.data.file.file_path;
111
+ }
112
+ /**
113
+ * 处理图片 URL(下载/转换 + 上传)
114
+ */
115
+ export async function processImageUrl(url, options) {
116
+ // 没有 token,检查是否需要上传
117
+ if (!options?.token) {
118
+ if (isBase64Image(url)) {
119
+ throw new Error('Base64 images must be uploaded to Mfuns server. ' +
120
+ 'Please provide a token in options.');
121
+ }
122
+ // 网络图片保留原 URL
123
+ return url;
124
+ }
125
+ // 有 token,处理上传
126
+ let file;
127
+ if (isBase64Image(url)) {
128
+ file = base64ToFile(url);
129
+ }
130
+ else if (isImageUrl(url)) {
131
+ file = await downloadImage(url);
132
+ }
133
+ else {
134
+ // 不是图片 URL,原样返回
135
+ return url;
136
+ }
137
+ return uploadImageToMfuns(file, options);
138
+ }
139
+ /**
140
+ * 并发控制处理器
141
+ */
142
+ export class ConcurrentProcessor {
143
+ constructor(processor, concurrency) {
144
+ this.processor = processor;
145
+ this.concurrency = concurrency;
146
+ this.queue = [];
147
+ this.running = 0;
148
+ this.results = new Map();
149
+ }
150
+ async process(items) {
151
+ return new Promise((resolve, reject) => {
152
+ let completed = 0;
153
+ let hasError = false;
154
+ const checkComplete = () => {
155
+ if (hasError)
156
+ return;
157
+ if (completed === items.length) {
158
+ const ordered = items.map((_, i) => this.results.get(i));
159
+ resolve(ordered);
160
+ }
161
+ };
162
+ const runNext = () => {
163
+ if (hasError)
164
+ return;
165
+ while (this.running < this.concurrency && this.queue.length > 0) {
166
+ const task = this.queue.shift();
167
+ this.running++;
168
+ // 找到这个 task 对应的 index
169
+ const index = items.indexOf(task.item);
170
+ this.processor(task.item, index)
171
+ .then((result) => {
172
+ if (hasError)
173
+ return;
174
+ this.results.set(index, result);
175
+ task.resolve(result);
176
+ })
177
+ .catch((error) => {
178
+ if (hasError)
179
+ return;
180
+ hasError = true;
181
+ task.reject(error);
182
+ reject(error);
183
+ })
184
+ .finally(() => {
185
+ this.running--;
186
+ completed++;
187
+ checkComplete();
188
+ runNext();
189
+ });
190
+ }
191
+ };
192
+ // 将任务加入队列
193
+ items.forEach((item) => {
194
+ new Promise((res, rej) => {
195
+ this.queue.push({ item, resolve: res, reject: rej });
196
+ });
197
+ });
198
+ runNext();
199
+ });
200
+ }
201
+ }
202
+ /**
203
+ * 批量处理图片 URL
204
+ */
205
+ export async function processImageUrls(urls, options, onError) {
206
+ const processor = new ConcurrentProcessor(async (url) => {
207
+ try {
208
+ return await processImageUrl(url, options);
209
+ }
210
+ catch (error) {
211
+ if (onError) {
212
+ const action = onError(error, url);
213
+ if (action === 'skip') {
214
+ return '';
215
+ }
216
+ else if (action === 'keep-original') {
217
+ return url;
218
+ }
219
+ }
220
+ throw error;
221
+ }
222
+ }, CONCURRENT_UPLOADS);
223
+ return processor.process(urls);
224
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "markdown-to-mfuns-json",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "将 Markdown 转换为喵御宅(Mfuns)定制的 Quill Delta JSON 格式",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc && node scripts/build.js",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "markdown",
30
+ "quill",
31
+ "delta",
32
+ "mfuns",
33
+ "喵御宅",
34
+ "json"
35
+ ],
36
+ "author": "Amoyens1s <amoyensis@outlook.com>",
37
+ "license": "MIT",
38
+ "devDependencies": {
39
+ "typescript": "^5.3.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ }
44
+ }