@fraqjs/plugin-takumi 0.2.0 → 0.3.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.
- package/dist/index.d.mts +10 -9
- package/dist/index.mjs +110 -57
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Context, Disposable } from "@fraqjs/fraq";
|
|
2
|
-
import {
|
|
2
|
+
import { FontLoader, RenderOptions, Renderer } from "@takumi-rs/core";
|
|
3
3
|
import { ReactElementLike } from "@takumi-rs/helpers";
|
|
4
4
|
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
5
5
|
import { ReactNode } from "react";
|
|
6
|
-
|
|
7
6
|
//#region src/service.d.ts
|
|
8
7
|
interface TakumiServiceOptions {
|
|
9
|
-
renderer?: ConstructRendererOptions;
|
|
10
8
|
renderDefaults?: RenderOptions;
|
|
11
9
|
onFontRegisterConflict?: 'error' | 'warn-and-ignore' | 'warn-and-replace';
|
|
12
10
|
}
|
|
@@ -21,16 +19,19 @@ declare class TakumiService implements Disposable {
|
|
|
21
19
|
private readonly ctx?;
|
|
22
20
|
readonly renderer: Renderer;
|
|
23
21
|
private readonly abortController;
|
|
22
|
+
private readonly imageFetchCache;
|
|
24
23
|
private readonly registeredFontFamilies;
|
|
25
24
|
private onFontRegisterConflict;
|
|
26
25
|
constructor(options?: TakumiServiceOptions | undefined, ctx?: Context | undefined);
|
|
27
|
-
registerFontFamily(family: string, fonts: (string | PathBasedFontDetails |
|
|
28
|
-
renderJsx(jsx: ReactNode | ReactElementLike, renderOptions?: RenderOptions, signal?: AbortSignal): Promise<Buffer>;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
renderHtmlWithEmoji(html: string, renderOptions?: RenderOptions, signal?: AbortSignal, emojiType?: EmojiType): Promise<Buffer>;
|
|
26
|
+
registerFontFamily(family: string, fonts: (string | PathBasedFontDetails | FontLoader)[], signal?: AbortSignal): Promise<void>;
|
|
27
|
+
renderJsx(jsx: ReactNode | ReactElementLike, renderOptions?: RenderOptions, signal?: AbortSignal, emojiType?: EmojiType): Promise<Buffer>;
|
|
28
|
+
renderHtml(html: string, renderOptions?: RenderOptions, signal?: AbortSignal, emojiType?: EmojiType): Promise<Buffer>;
|
|
29
|
+
private renderNode;
|
|
32
30
|
private mergeRenderOptions;
|
|
33
|
-
private
|
|
31
|
+
private mergeFonts;
|
|
32
|
+
private mergeImages;
|
|
33
|
+
private toImagesInput;
|
|
34
|
+
private toFontLoader;
|
|
34
35
|
dispose(): void;
|
|
35
36
|
}
|
|
36
37
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { definePlugin } from "@fraqjs/fraq";
|
|
3
|
-
import { Renderer
|
|
4
|
-
import {
|
|
3
|
+
import { Renderer } from "@takumi-rs/core";
|
|
4
|
+
import { fontFromUrl, prepareImages } from "@takumi-rs/helpers";
|
|
5
5
|
import { extractEmojis } from "@takumi-rs/helpers/emoji";
|
|
6
6
|
import { fromHtml } from "@takumi-rs/helpers/html";
|
|
7
7
|
import { fromJsx } from "@takumi-rs/helpers/jsx";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
//#region src/service.ts
|
|
10
10
|
function combineAbortSignals(...signals) {
|
|
11
|
-
|
|
11
|
+
const filteredSignals = signals.filter((signal) => signal !== void 0);
|
|
12
|
+
if (filteredSignals.length === 0) return;
|
|
13
|
+
if (filteredSignals.length === 1) return filteredSignals[0];
|
|
14
|
+
return AbortSignal.any(filteredSignals);
|
|
15
|
+
}
|
|
16
|
+
function isRemoteUrl(value) {
|
|
17
|
+
return value.startsWith("https://") || value.startsWith("http://");
|
|
18
|
+
}
|
|
19
|
+
function getImageSources(images) {
|
|
20
|
+
if (!images) return [];
|
|
21
|
+
if (Array.isArray(images)) return images;
|
|
22
|
+
return images.sources ?? [];
|
|
23
|
+
}
|
|
24
|
+
function getImageCache(images) {
|
|
25
|
+
if (!images || Array.isArray(images)) return;
|
|
26
|
+
return images.cache;
|
|
12
27
|
}
|
|
13
28
|
var TakumiService = class {
|
|
14
29
|
options;
|
|
15
30
|
ctx;
|
|
16
31
|
renderer;
|
|
17
32
|
abortController = new AbortController();
|
|
18
|
-
|
|
33
|
+
imageFetchCache = /* @__PURE__ */ new Map();
|
|
34
|
+
registeredFontFamilies = /* @__PURE__ */ new Map();
|
|
19
35
|
onFontRegisterConflict;
|
|
20
36
|
constructor(options, ctx) {
|
|
21
37
|
this.options = options;
|
|
22
38
|
this.ctx = ctx;
|
|
23
|
-
this.renderer = new Renderer(
|
|
39
|
+
this.renderer = new Renderer();
|
|
24
40
|
this.onFontRegisterConflict = options?.onFontRegisterConflict ?? "warn-and-ignore";
|
|
25
41
|
}
|
|
26
42
|
async registerFontFamily(family, fonts, signal) {
|
|
@@ -34,56 +50,50 @@ var TakumiService = class {
|
|
|
34
50
|
} else if (this.onFontRegisterConflict === "warn-and-replace") if (this.ctx) this.ctx.logger.warn(`${message} Replacing previous registration.`);
|
|
35
51
|
else console.warn(`${message} Replacing previous registration.`);
|
|
36
52
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
name: family,
|
|
40
|
-
data: await fs.readFile(font)
|
|
41
|
-
};
|
|
42
|
-
else if ("path" in font) {
|
|
43
|
-
const { path, name = family, weight, style } = font;
|
|
44
|
-
return {
|
|
45
|
-
name,
|
|
46
|
-
data: await fs.readFile(path),
|
|
47
|
-
weight,
|
|
48
|
-
style
|
|
49
|
-
};
|
|
50
|
-
} else return font;
|
|
51
|
-
})), combineAbortSignals(signal, this.abortController.signal));
|
|
52
|
-
this.registeredFontFamilies.add(family);
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
this.registeredFontFamilies.set(family, fonts.map((font) => this.toFontLoader(family, font)));
|
|
53
55
|
}
|
|
54
|
-
async renderJsx(jsx, renderOptions, signal) {
|
|
56
|
+
async renderJsx(jsx, renderOptions, signal, emojiType) {
|
|
55
57
|
const { node, stylesheets } = await fromJsx(jsx);
|
|
56
|
-
return
|
|
58
|
+
return await this.renderNode({
|
|
59
|
+
node,
|
|
57
60
|
stylesheets,
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
renderOptions,
|
|
62
|
+
signal,
|
|
63
|
+
emojiType
|
|
64
|
+
});
|
|
60
65
|
}
|
|
61
|
-
async
|
|
62
|
-
const { node, stylesheets } = await fromJsx(jsx);
|
|
63
|
-
const { node: processedNode, fetchedResources } = await this.processEmoji(node, emojiType);
|
|
64
|
-
return this.renderer.render(processedNode, this.mergeRenderOptions({
|
|
65
|
-
stylesheets,
|
|
66
|
-
fetchedResources,
|
|
67
|
-
userOptions: renderOptions
|
|
68
|
-
}), combineAbortSignals(signal, this.abortController.signal));
|
|
69
|
-
}
|
|
70
|
-
async renderHtml(html, renderOptions, signal) {
|
|
66
|
+
async renderHtml(html, renderOptions, signal, emojiType) {
|
|
71
67
|
const { node, stylesheets } = fromHtml(html);
|
|
72
|
-
return
|
|
68
|
+
return await this.renderNode({
|
|
69
|
+
node,
|
|
73
70
|
stylesheets,
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
renderOptions,
|
|
72
|
+
signal,
|
|
73
|
+
emojiType
|
|
74
|
+
});
|
|
76
75
|
}
|
|
77
|
-
async
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
stylesheets,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
async renderNode(components) {
|
|
77
|
+
const node = components.emojiType === void 0 ? components.node : extractEmojis(components.node, components.emojiType);
|
|
78
|
+
return await this.renderer.render(node, await this.mergeRenderOptions({
|
|
79
|
+
node,
|
|
80
|
+
stylesheets: components.stylesheets,
|
|
81
|
+
userOptions: components.renderOptions,
|
|
82
|
+
signal: components.signal,
|
|
83
|
+
images: components.emojiType !== void 0
|
|
84
|
+
}));
|
|
85
85
|
}
|
|
86
|
-
mergeRenderOptions(components) {
|
|
86
|
+
async mergeRenderOptions(components) {
|
|
87
|
+
const renderDefaults = this.options?.renderDefaults;
|
|
88
|
+
const signal = combineAbortSignals(components.signal, components.userOptions?.signal, this.abortController.signal);
|
|
89
|
+
const images = await this.mergeImages({
|
|
90
|
+
node: components.node,
|
|
91
|
+
prepareImages: components.images ?? false,
|
|
92
|
+
signal,
|
|
93
|
+
defaultImages: renderDefaults?.images,
|
|
94
|
+
userImages: components.userOptions?.images
|
|
95
|
+
});
|
|
96
|
+
const fonts = this.mergeFonts(renderDefaults?.fonts, components.userOptions?.fonts);
|
|
87
97
|
return {
|
|
88
98
|
...this.options?.renderDefaults,
|
|
89
99
|
...components.userOptions,
|
|
@@ -92,20 +102,63 @@ var TakumiService = class {
|
|
|
92
102
|
...components.stylesheets ?? [],
|
|
93
103
|
...components.userOptions?.stylesheets ?? []
|
|
94
104
|
],
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
...components.userOptions?.fetchedResources ?? []
|
|
99
|
-
]
|
|
105
|
+
...fonts ? { fonts } : {},
|
|
106
|
+
...images ? { images } : {},
|
|
107
|
+
...signal ? { signal } : {}
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
mergeFonts(defaultFonts, userFonts) {
|
|
111
|
+
const fonts = [...this.registeredFontFamilies.values()].flat();
|
|
112
|
+
if (defaultFonts) fonts.push(...defaultFonts);
|
|
113
|
+
if (userFonts) fonts.push(...userFonts);
|
|
114
|
+
return fonts.length > 0 ? fonts : void 0;
|
|
115
|
+
}
|
|
116
|
+
async mergeImages(components) {
|
|
117
|
+
const sources = [...getImageSources(components.defaultImages), ...getImageSources(components.userImages)];
|
|
118
|
+
const cache = getImageCache(components.userImages) ?? getImageCache(components.defaultImages);
|
|
119
|
+
if (components.prepareImages) {
|
|
120
|
+
const preparedSources = await prepareImages({
|
|
121
|
+
node: components.node,
|
|
122
|
+
sources,
|
|
123
|
+
fetchCache: this.imageFetchCache,
|
|
124
|
+
signal: components.signal,
|
|
125
|
+
throwOnError: false
|
|
126
|
+
});
|
|
127
|
+
return this.toImagesInput(preparedSources, cache);
|
|
128
|
+
}
|
|
129
|
+
return this.toImagesInput(sources, cache);
|
|
130
|
+
}
|
|
131
|
+
toImagesInput(sources, cache) {
|
|
132
|
+
if (sources.length === 0) return cache === void 0 ? void 0 : { cache };
|
|
133
|
+
return cache === void 0 ? sources : {
|
|
134
|
+
sources,
|
|
135
|
+
cache
|
|
107
136
|
};
|
|
108
137
|
}
|
|
138
|
+
toFontLoader(family, font) {
|
|
139
|
+
if (typeof font === "string") {
|
|
140
|
+
if (isRemoteUrl(font)) return {
|
|
141
|
+
...fontFromUrl(font),
|
|
142
|
+
name: family
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
name: family,
|
|
146
|
+
key: `${family}:${font}`,
|
|
147
|
+
data: () => fs.readFile(font)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if ("path" in font) {
|
|
151
|
+
const { path, name = family, weight, style } = font;
|
|
152
|
+
return {
|
|
153
|
+
name,
|
|
154
|
+
key: `${name}:${path}:${weight ?? ""}:${style ?? ""}`,
|
|
155
|
+
weight,
|
|
156
|
+
style,
|
|
157
|
+
data: () => fs.readFile(path)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return font;
|
|
161
|
+
}
|
|
109
162
|
dispose() {
|
|
110
163
|
this.abortController.abort();
|
|
111
164
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraqjs/plugin-takumi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Takumi integration plugin for Fraq",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": "git+https://github.com/fraqjs/fraq.git"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://fraq.
|
|
18
|
+
"homepage": "https://fraq.dev/",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": "^19.0.0",
|
|
22
|
-
"@fraqjs/fraq": "^0.
|
|
22
|
+
"@fraqjs/fraq": "^0.13.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@fraqjs/takumi-builtin-fonts": "^1.0.0",
|
|
26
|
-
"@takumi-rs/core": "^
|
|
27
|
-
"@takumi-rs/helpers": "^
|
|
26
|
+
"@takumi-rs/core": "^2.0.1",
|
|
27
|
+
"@takumi-rs/helpers": "^2.0.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/react": "^19.2.15",
|