astro-openmoji 0.0.0-stage → 0.1.1
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/ATTRIBUTION.md +18 -0
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +82 -0
- package/dist/options-DnDZhR4N.d.ts +10 -0
- package/dist/rehype.d.ts +952 -0
- package/dist/rehype.js +14 -0
- package/dist/transform-CnWUZewM.js +107 -0
- package/package.json +69 -4
package/ATTRIBUTION.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# OpenMoji attribution
|
|
2
|
+
|
|
3
|
+
This package uses SVG artwork from the OpenMoji project:
|
|
4
|
+
|
|
5
|
+
- Project: [OpenMoji](https://openmoji.org/)
|
|
6
|
+
- Source: [`openmoji` on npm](https://www.npmjs.com/package/openmoji)
|
|
7
|
+
- Creators: HfG Schwäbisch Gmünd, Benedikt Groß, Daniel Utz, 70+ students, and external contributors
|
|
8
|
+
- Artwork license: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
9
|
+
|
|
10
|
+
`astro-openmoji` copies only the SVG files used by a site's Markdown from the installed `openmoji` package into the site's build output. The SVG artwork is copied unchanged by this integration.
|
|
11
|
+
|
|
12
|
+
Suggested visible website credit:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Emoji by OpenMoji — CC BY-SA 4.0
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The integration code itself is licensed under the MIT License. See [LICENSE](./LICENSE).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 astro-openmoji contributors
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,119 @@
|
|
|
1
|
-
#
|
|
1
|
+
# astro-openmoji
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
在 Astro 构建 Markdown 时,把源码中的原生 Unicode Emoji 转换为本地 [OpenMoji](https://openmoji.org/) SVG。
|
|
4
|
+
|
|
5
|
+
- Markdown 源文件保持不变
|
|
6
|
+
- 不使用客户端 JavaScript
|
|
7
|
+
- 不依赖 CDN,可离线部署
|
|
8
|
+
- 支持 ZWJ、肤色、旗帜及变体选择符等完整 Emoji 序列
|
|
9
|
+
- `astro dev` 按需提供 SVG,`astro build` 只复制实际使用的资源
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install astro-openmoji
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
本包面向 Astro 7 和 Node.js 22.12 或更高版本。
|
|
18
|
+
|
|
19
|
+
## 配置
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// astro.config.ts
|
|
23
|
+
import { defineConfig } from "astro/config";
|
|
24
|
+
import openmoji from "astro-openmoji";
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
integrations: [openmoji()],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
之后正常在 Markdown 中输入原生 Emoji:
|
|
32
|
+
|
|
33
|
+
```md
|
|
34
|
+
今天学 CMake 学得有些 😵💫,但总算跑通了 🚀
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
构建后的 HTML 类似:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<p>
|
|
41
|
+
今天学 CMake 学得有些
|
|
42
|
+
<img class="openmoji" src="/openmoji/1F635-200D-1F4AB.svg" alt="😵💫" style="width:1em;height:1em;vertical-align:-0.125em;display:inline-block">
|
|
43
|
+
,但总算跑通了
|
|
44
|
+
<img class="openmoji" src="/openmoji/1F680.svg" alt="🚀" style="width:1em;height:1em;vertical-align:-0.125em;display:inline-block">
|
|
45
|
+
</p>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`alt` 始终保留原 Emoji。内联样式只负责把一 em 大小的图片与文字基线自然对齐。
|
|
49
|
+
|
|
50
|
+
## 配置项
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
openmoji({
|
|
54
|
+
style: "color",
|
|
55
|
+
assetDir: "openmoji",
|
|
56
|
+
className: "openmoji",
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| `style` | `"color" \| "black"` | `"color"` | 使用 OpenMoji 彩色或黑色 SVG |
|
|
63
|
+
| `assetDir` | `string` | `"openmoji"` | 输出目录和公共 URL 路径,可使用 `emoji/assets` 这样的相对路径 |
|
|
64
|
+
| `className` | `string` | `"openmoji"` | 生成的 `<img>` 使用的单个 CSS 类名 |
|
|
65
|
+
|
|
66
|
+
Astro 的 `base` 会自动加入图片 URL。例如 `base: "/blog"` 时,图片地址为 `/blog/openmoji/1F680.svg`,资源仍输出到 `dist/openmoji/`。
|
|
67
|
+
|
|
68
|
+
## 转换范围
|
|
69
|
+
|
|
70
|
+
插件只处理 HAST 普通文本节点,因此段落、标题、列表、引用和链接文字中的 Emoji 会被替换。以下内容不会被替换:
|
|
71
|
+
|
|
72
|
+
- 行内代码和代码块中的文本
|
|
73
|
+
- 原始 HTML 属性
|
|
74
|
+
- URL 属性
|
|
75
|
+
- frontmatter
|
|
76
|
+
- OpenMoji 中不存在的 Emoji;这类字符会原样保留
|
|
77
|
+
|
|
78
|
+
匹配使用 `emoji-regex`,不会把 ZWJ、肤色或旗帜序列拆成单个字符。插件先查找完整码点文件;如果不存在,再去除 `FE0E` / `FE0F` 变体选择符重试。例如 `❤️` 会使用 `2764.svg`。
|
|
79
|
+
|
|
80
|
+
## 开发与生产构建
|
|
81
|
+
|
|
82
|
+
在 `astro dev` 中,integration 通过 Vite 开发服务器中间件从已安装的 `openmoji/<style>/svg/` 按需返回 `/openmoji/*.svg`,无需复制到 `public/`。
|
|
83
|
+
|
|
84
|
+
在 `astro build` 中,Rehype 转换会记录本次构建实际引用的文件;构建完成后仅将这些 SVG 复制到 `dist/<assetDir>/`。Markdown、`public/` 和其他工作区文件均不会被修改。
|
|
85
|
+
|
|
86
|
+
Astro 7 默认使用 Sätteri,而 Rehype 插件只能运行在 Unified 流水线中。因此 integration 会自动选择官方 `@astrojs/markdown-remark` Unified processor;如果项目已经配置 Unified,则会保留现有选项并追加本插件。显式使用 Sätteri 插件的项目需要先确认改用 Unified 后功能仍符合预期。
|
|
87
|
+
|
|
88
|
+
## 单独使用 Rehype 插件
|
|
89
|
+
|
|
90
|
+
高级用户可以直接注册底层插件:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { rehypeOpenmoji } from "astro-openmoji/rehype";
|
|
94
|
+
|
|
95
|
+
// unified().use(rehypeOpenmoji, { style: "black", base: "/blog" })
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
直接使用时支持与 integration 相同的三个选项,另可通过 `base` 设置 URL 前缀。单独使用 Rehype 插件只转换 HAST,不负责开发服务器或生产资源复制。
|
|
99
|
+
|
|
100
|
+
## 本项目开发
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install
|
|
104
|
+
npm test
|
|
105
|
+
npm run build
|
|
106
|
+
npm pack --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
测试包含独立 Rehype 转换、真实 `astro build` fixture、`base: "/blog"`、最小资源输出,以及真实 `astro dev` SVG 请求。
|
|
110
|
+
|
|
111
|
+
## 许可证与署名
|
|
112
|
+
|
|
113
|
+
`astro-openmoji` 插件代码使用 [MIT License](./LICENSE)。OpenMoji 图形资产由 OpenMoji 项目提供,使用 [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/);详情见 [ATTRIBUTION.md](./ATTRIBUTION.md)。
|
|
114
|
+
|
|
115
|
+
建议在网站页脚或关于页面中显示:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
Emoji by OpenMoji — CC BY-SA 4.0
|
|
119
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { i as openmojiAssetRoot, n as normalizeBase, r as resolveOptions, t as transformOpenmojiTree } from "./transform-CnWUZewM.js";
|
|
2
|
+
import { copyFile, mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { isUnifiedProcessor, unified } from "@astrojs/markdown-remark";
|
|
4
|
+
// src/index.ts
|
|
5
|
+
const svgFilenamePattern = /^[0-9A-F]{4,6}(?:-[0-9A-F]{4,6})*\.svg$/;
|
|
6
|
+
function openmoji(options = {}) {
|
|
7
|
+
const resolved = resolveOptions(options);
|
|
8
|
+
const assetRoot = openmojiAssetRoot(resolved.style);
|
|
9
|
+
const usedAssets = /* @__PURE__ */ new Set();
|
|
10
|
+
const state = { base: "/" };
|
|
11
|
+
const plugin = () => (tree) => {
|
|
12
|
+
transformOpenmojiTree(tree, {
|
|
13
|
+
...resolved,
|
|
14
|
+
base: state.base,
|
|
15
|
+
assetRoot,
|
|
16
|
+
onAsset: (filename) => usedAssets.add(filename)
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
name: "astro-openmoji",
|
|
21
|
+
hooks: {
|
|
22
|
+
"astro:config:setup": ({ config, updateConfig }) => {
|
|
23
|
+
state.base = normalizeBase(config.base);
|
|
24
|
+
const processor = config.markdown?.processor;
|
|
25
|
+
if (processor === void 0 || !isUnifiedProcessor(processor)) {
|
|
26
|
+
updateConfig({ markdown: { processor: unified({ rehypePlugins: [plugin] }) } });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
processor.options.rehypePlugins.push(plugin);
|
|
30
|
+
},
|
|
31
|
+
"astro:config:done": ({ config }) => {
|
|
32
|
+
state.base = normalizeBase(config.base);
|
|
33
|
+
},
|
|
34
|
+
"astro:server:setup": ({ server }) => {
|
|
35
|
+
const assetPrefix = `${state.base}${resolved.assetDir}/`;
|
|
36
|
+
server.middlewares.use(async (request, response, next) => {
|
|
37
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
38
|
+
next();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let pathname;
|
|
42
|
+
try {
|
|
43
|
+
pathname = decodeURIComponent(new URL(request.url ?? "/", "http://localhost").pathname);
|
|
44
|
+
} catch {
|
|
45
|
+
next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!pathname.startsWith(assetPrefix)) {
|
|
49
|
+
next();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const filename = pathname.slice(assetPrefix.length);
|
|
53
|
+
if (!svgFilenamePattern.test(filename)) {
|
|
54
|
+
next();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const svg = await readFile(new URL(filename, assetRoot));
|
|
59
|
+
response.statusCode = 200;
|
|
60
|
+
response.setHeader("Content-Type", "image/svg+xml; charset=utf-8");
|
|
61
|
+
response.setHeader("Cache-Control", "public, max-age=3600");
|
|
62
|
+
response.setHeader("Content-Length", svg.byteLength);
|
|
63
|
+
response.end(request.method === "HEAD" ? void 0 : svg);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error.code === "ENOENT") next();
|
|
66
|
+
else next(error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
"astro:build:start": () => {
|
|
71
|
+
usedAssets.clear();
|
|
72
|
+
},
|
|
73
|
+
"astro:build:done": async ({ dir }) => {
|
|
74
|
+
if (usedAssets.size === 0) return;
|
|
75
|
+
const outputRoot = new URL(`${resolved.assetDir}/`, dir);
|
|
76
|
+
await mkdir(outputRoot, { recursive: true });
|
|
77
|
+
await Promise.all([...usedAssets].map((filename) => copyFile(new URL(filename, assetRoot), new URL(filename, outputRoot))));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export { openmoji as default };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type OpenmojiStyle = "color" | "black";
|
|
2
|
+
interface OpenmojiOptions {
|
|
3
|
+
/** OpenMoji artwork style. @default "color" */
|
|
4
|
+
style?: OpenmojiStyle;
|
|
5
|
+
/** Public URL directory for emitted SVG files. @default "openmoji" */
|
|
6
|
+
assetDir?: string;
|
|
7
|
+
/** CSS class placed on generated image elements. @default "openmoji" */
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export { OpenmojiStyle as n, OpenmojiOptions as t };
|