rspress-plugin-map 0.1.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/README.md +124 -0
- package/dist/components/RspressMap.d.ts +19 -0
- package/dist/components/RspressMap.js +170 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +38 -0
- package/dist/rehype-rspress-map.d.ts +7 -0
- package/dist/rehype-rspress-map.js +50 -0
- package/dist/remark-rspress-map.d.ts +8 -0
- package/dist/remark-rspress-map.js +72 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# rspress-plugin-map
|
|
2
|
+
|
|
3
|
+
Rspress v2 插件,用于在文章中插入交互式地图,支持 Google 地图、高德地图、百度地图、Geoq 地图和 OpenStreetMap。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 支持多种地图类型:混合地图(可切换多个图层)、Google 地图、高德地图、百度地图、Geoq 地图、OpenStreetMap
|
|
8
|
+
- 支持标记点和提示文本
|
|
9
|
+
- 支持自定义地图容器宽高、缩放等级、经纬度等参数
|
|
10
|
+
- 使用 HTML 标签方式调用,简单易用
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install rspress-plugin-map --save
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 配置
|
|
19
|
+
|
|
20
|
+
在 `rspress.config.ts` 中添加插件配置:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { defineConfig } from 'rspress/config';
|
|
24
|
+
import pluginMap from 'rspress-plugin-map';
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
plugins: [
|
|
28
|
+
pluginMap()
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 使用
|
|
34
|
+
|
|
35
|
+
在 Markdown 文件中使用 `<rspress-map>` 标签插入地图:
|
|
36
|
+
|
|
37
|
+
### 基本用法
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<rspress-map></rspress-map>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 自定义参数
|
|
44
|
+
|
|
45
|
+
所有参数都是可选的,未指定时使用默认值:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<rspress-map
|
|
49
|
+
type="hybrid"
|
|
50
|
+
lat="39.9042"
|
|
51
|
+
lng="116.4074"
|
|
52
|
+
zoom="12"
|
|
53
|
+
width="100%"
|
|
54
|
+
height="500px"
|
|
55
|
+
marker="true"
|
|
56
|
+
marker-text="北京市"
|
|
57
|
+
></rspress-map>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 参数说明
|
|
61
|
+
|
|
62
|
+
| 参数 | 说明 | 默认值 | 可选值 |
|
|
63
|
+
|------|------|--------|--------|
|
|
64
|
+
| type | 地图类型 | hybrid | hybrid, google, gaode, baidu, geoq, openstreet |
|
|
65
|
+
| lat | 纬度 | 39.9042 | 数值 |
|
|
66
|
+
| lng | 经度 | 116.4074 | 数值 |
|
|
67
|
+
| zoom | 缩放等级 | 10 | 1-18 |
|
|
68
|
+
| width | 容器宽度 | 100% | CSS 尺寸值 |
|
|
69
|
+
| height | 容器高度 | 400px | CSS 尺寸值 |
|
|
70
|
+
| marker | 是否显示标记点 | true | true, false |
|
|
71
|
+
| marker-text | 标记点提示文本 | "" | 字符串 |
|
|
72
|
+
|
|
73
|
+
### 地图类型说明
|
|
74
|
+
|
|
75
|
+
- **hybrid**: 混合地图,支持切换 Google、高德、百度、Geoq、OpenStreetMap 等多个图层
|
|
76
|
+
- **google**: Google 地图
|
|
77
|
+
- **gaode**: 高德地图
|
|
78
|
+
- **baidu**: 百度地图
|
|
79
|
+
- **geoq**: Geoq 地图
|
|
80
|
+
- **openstreet**: OpenStreetMap
|
|
81
|
+
|
|
82
|
+
## 注意事项
|
|
83
|
+
|
|
84
|
+
1. 插件会自动加载所需的地图脚本,无需手动引入。
|
|
85
|
+
|
|
86
|
+
2. 混合地图使用 Leaflet 实现,依赖 `leaflet` 和 `leaflet.chinatmsproviders` 库。
|
|
87
|
+
|
|
88
|
+
## 示例
|
|
89
|
+
|
|
90
|
+
### 混合地图
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<rspress-map type="hybrid" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Google 地图
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<rspress-map type="google" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 高德地图
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<rspress-map type="gaode" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 百度地图
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<rspress-map type="baidu" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Geoq 地图
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<rspress-map type="geoq" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### OpenStreetMap
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<rspress-map type="openstreet" lat="39.9042" lng="116.4074" zoom="12" marker-text="北京市"></rspress-map>
|
|
124
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface RspressMapProps {
|
|
3
|
+
type?: 'hybrid' | 'google' | 'gaode' | 'baidu' | 'geoq' | 'openstreet' | string;
|
|
4
|
+
lat?: string | number;
|
|
5
|
+
lng?: string | number;
|
|
6
|
+
zoom?: string | number;
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
marker?: boolean | string;
|
|
10
|
+
markerText?: string;
|
|
11
|
+
layerType?: number | string;
|
|
12
|
+
}
|
|
13
|
+
declare global {
|
|
14
|
+
interface Window {
|
|
15
|
+
L: any;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export declare const RspressMap: React.FC<RspressMapProps>;
|
|
19
|
+
export default RspressMap;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RspressMap = void 0;
|
|
13
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
14
|
+
const react_1 = require("react");
|
|
15
|
+
const loadScript = (src) => {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
if (typeof window !== 'undefined' && document.querySelector(`script[src="${src}"]`)) {
|
|
18
|
+
// Already added, wait for it to load if it hasn't mapped to window yet
|
|
19
|
+
let attempts = 0;
|
|
20
|
+
const check = () => {
|
|
21
|
+
if (window.L && src.includes('leaflet@'))
|
|
22
|
+
return resolve();
|
|
23
|
+
if (window.L && window.L.tileLayer && window.L.tileLayer.chinaProvider && src.includes('ChineseTmsProviders'))
|
|
24
|
+
return resolve();
|
|
25
|
+
attempts++;
|
|
26
|
+
if (attempts > 50)
|
|
27
|
+
return reject(new Error(`Timeout loading ${src}`)); // 5 seconds
|
|
28
|
+
setTimeout(check, 100);
|
|
29
|
+
};
|
|
30
|
+
check();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const script = document.createElement('script');
|
|
34
|
+
script.src = src;
|
|
35
|
+
script.onload = () => resolve();
|
|
36
|
+
script.onerror = (e) => reject(e);
|
|
37
|
+
document.head.appendChild(script);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const loadCss = (href) => {
|
|
41
|
+
if (typeof window !== 'undefined' && document.querySelector(`link[href="${href}"]`))
|
|
42
|
+
return;
|
|
43
|
+
const link = document.createElement('link');
|
|
44
|
+
link.rel = 'stylesheet';
|
|
45
|
+
link.href = href;
|
|
46
|
+
document.head.appendChild(link);
|
|
47
|
+
};
|
|
48
|
+
const RspressMap = (props) => {
|
|
49
|
+
const { type = 'gaode', lat = 39.9042, lng = 116.4074, zoom = 10, width = '100%', height = '400px', marker = true, markerText = '', layerType } = props;
|
|
50
|
+
const mapRef = (0, react_1.useRef)(null);
|
|
51
|
+
const leafletMapRef = (0, react_1.useRef)(null);
|
|
52
|
+
const [isLoaded, setIsLoaded] = (0, react_1.useState)(false);
|
|
53
|
+
const id = (0, react_1.useRef)(`map-${Math.random().toString(36).substr(2, 9)}`);
|
|
54
|
+
(0, react_1.useEffect)(() => {
|
|
55
|
+
if (typeof window === 'undefined')
|
|
56
|
+
return;
|
|
57
|
+
const initLeaflet = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
|
+
try {
|
|
59
|
+
loadCss('//unpkg.com/hexo-tag-map/lib/leaflet@1.7.1.css');
|
|
60
|
+
yield loadScript('//unpkg.com/hexo-tag-map/lib/leaflet@1.7.1.js');
|
|
61
|
+
yield loadScript('//unpkg.com/hexo-tag-map/lib/leaflet.ChineseTmsProviders@1.0.4.js');
|
|
62
|
+
setIsLoaded(true);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error('Failed to load map scripts:', error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (window.L && window.L.tileLayer && window.L.tileLayer.chinaProvider) {
|
|
69
|
+
setIsLoaded(true);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
initLeaflet();
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
(0, react_1.useEffect)(() => {
|
|
76
|
+
if (!isLoaded || !mapRef.current)
|
|
77
|
+
return;
|
|
78
|
+
const latNum = Number(lat);
|
|
79
|
+
const lngNum = Number(lng);
|
|
80
|
+
const zoomNum = Number(zoom);
|
|
81
|
+
const showMarker = marker === true || marker === 'true';
|
|
82
|
+
const L = window.L;
|
|
83
|
+
// Cleanup previous map instance if it exists
|
|
84
|
+
if (leafletMapRef.current) {
|
|
85
|
+
leafletMapRef.current.remove();
|
|
86
|
+
leafletMapRef.current = null;
|
|
87
|
+
}
|
|
88
|
+
// Setup providers based on hexo-tag-map config
|
|
89
|
+
const normalm = L.tileLayer.chinaProvider('GaoDe.Normal.Map', { maxZoom: 20, minZoom: 1, attribution: '高德地图' });
|
|
90
|
+
const imgm = L.tileLayer.chinaProvider('GaoDe.Satellite.Map', { maxZoom: 20, minZoom: 1, attribution: '高德地图' });
|
|
91
|
+
const imga = L.tileLayer.chinaProvider('GaoDe.Satellite.Annotion', { maxZoom: 20, minZoom: 1, attribution: '高德地图' });
|
|
92
|
+
const googleNormal = L.tileLayer.chinaProvider('Google.Normal.Map', { maxZoom: 21, minZoom: 1, attribution: 'Google Maps' });
|
|
93
|
+
const googleSatellite = L.tileLayer.chinaProvider('Google.Satellite.Map', { maxZoom: 21, minZoom: 1, attribution: 'Google Maps' });
|
|
94
|
+
const googleRoute = L.tileLayer.chinaProvider('Google.Satellite.Annotion', { maxZoom: 21, minZoom: 1, attribution: 'Google Maps' });
|
|
95
|
+
const geoqNormal = L.tileLayer.chinaProvider('Geoq.Normal.Map', { maxZoom: 21, minZoom: 1, attribution: 'GeoQ' });
|
|
96
|
+
const normalGaode = L.layerGroup([normalm]);
|
|
97
|
+
const imageGaode = L.layerGroup([imgm, imga]);
|
|
98
|
+
// Define base layers control
|
|
99
|
+
const baseLayers = {
|
|
100
|
+
"高德地图": normalGaode,
|
|
101
|
+
"智图地图": geoqNormal,
|
|
102
|
+
"谷歌地图": googleNormal,
|
|
103
|
+
"高德卫星地图": imgm,
|
|
104
|
+
"谷歌卫星地图": googleSatellite,
|
|
105
|
+
"高德卫星标注": imageGaode,
|
|
106
|
+
"谷歌卫星标注": googleRoute
|
|
107
|
+
};
|
|
108
|
+
let defaultLayer = normalGaode;
|
|
109
|
+
// Follow hexo-tag-map layer logic if layerType (tuceng) is specifically provided
|
|
110
|
+
if (layerType !== undefined) {
|
|
111
|
+
const layerNum = Number(layerType);
|
|
112
|
+
if (layerNum === 2)
|
|
113
|
+
defaultLayer = geoqNormal;
|
|
114
|
+
else if (layerNum === 3)
|
|
115
|
+
defaultLayer = googleNormal;
|
|
116
|
+
else if (layerNum === 4)
|
|
117
|
+
defaultLayer = imgm;
|
|
118
|
+
else if (layerNum === 5)
|
|
119
|
+
defaultLayer = googleSatellite;
|
|
120
|
+
else if (layerNum === 6)
|
|
121
|
+
defaultLayer = imageGaode;
|
|
122
|
+
else if (layerNum === 7)
|
|
123
|
+
defaultLayer = googleRoute;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Select the default layer based on type if no layerType is provided
|
|
127
|
+
if (type === 'google')
|
|
128
|
+
defaultLayer = googleNormal;
|
|
129
|
+
else if (type === 'hybrid')
|
|
130
|
+
defaultLayer = imageGaode;
|
|
131
|
+
else if (type === 'geoq')
|
|
132
|
+
defaultLayer = geoqNormal;
|
|
133
|
+
else if (type === 'baidu')
|
|
134
|
+
defaultLayer = geoqNormal; // Fallback since Baidu uses different coords traditionally, mapping it to GeoQ or Gaode is safer for standard WGS84 coords
|
|
135
|
+
}
|
|
136
|
+
const map = L.map(mapRef.current, {
|
|
137
|
+
center: [latNum, lngNum],
|
|
138
|
+
zoom: zoomNum,
|
|
139
|
+
layers: [defaultLayer],
|
|
140
|
+
zoomControl: false // hexo-tag-map disables default to add custom one
|
|
141
|
+
});
|
|
142
|
+
L.control.layers(baseLayers, null).addTo(map);
|
|
143
|
+
L.control.zoom({ zoomInTitle: '放大', zoomOutTitle: '缩小' }).addTo(map);
|
|
144
|
+
if (showMarker) {
|
|
145
|
+
const m = L.marker([latNum, lngNum]).addTo(map);
|
|
146
|
+
if (markerText) {
|
|
147
|
+
m.bindPopup(markerText).openPopup();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
leafletMapRef.current = map;
|
|
151
|
+
return () => {
|
|
152
|
+
if (leafletMapRef.current) {
|
|
153
|
+
leafletMapRef.current.remove();
|
|
154
|
+
leafletMapRef.current = null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}, [isLoaded, lat, lng, zoom, type, marker, markerText, layerType]);
|
|
158
|
+
const heightValue = height || '400px';
|
|
159
|
+
const widthValue = width || '100%';
|
|
160
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "map-box", style: { margin: '0.8rem 0 1.6rem 0' }, children: (0, jsx_runtime_1.jsx)("div", { id: id.current, ref: mapRef, style: {
|
|
161
|
+
maxWidth: widthValue,
|
|
162
|
+
height: heightValue,
|
|
163
|
+
display: 'block',
|
|
164
|
+
margin: '0 auto',
|
|
165
|
+
zIndex: 1,
|
|
166
|
+
borderRadius: '5px'
|
|
167
|
+
} }) }));
|
|
168
|
+
};
|
|
169
|
+
exports.RspressMap = RspressMap;
|
|
170
|
+
exports.default = exports.RspressMap;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RspressPlugin } from '@rspress/core';
|
|
2
|
+
/**
|
|
3
|
+
* Rspress map plugin options
|
|
4
|
+
*/
|
|
5
|
+
export interface MapPluginOptions {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Rspress plugin for embedding interactive maps
|
|
9
|
+
*/
|
|
10
|
+
export declare function pluginMap(options?: MapPluginOptions): RspressPlugin;
|
|
11
|
+
export default pluginMap;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pluginMap = pluginMap;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const remark_rspress_map_1 = require("./remark-rspress-map");
|
|
9
|
+
const rehype_rspress_map_1 = require("./rehype-rspress-map");
|
|
10
|
+
/**
|
|
11
|
+
* Rspress plugin for embedding interactive maps
|
|
12
|
+
*/
|
|
13
|
+
function pluginMap(options = {}) {
|
|
14
|
+
return {
|
|
15
|
+
name: 'rspress-plugin-map',
|
|
16
|
+
// Register the RspressMap component as a global component available in MDX
|
|
17
|
+
markdown: {
|
|
18
|
+
globalComponents: [
|
|
19
|
+
path_1.default.join(__dirname, 'components', 'RspressMap.js')
|
|
20
|
+
],
|
|
21
|
+
// Use remark plugin to transform <rspress-map> HTML tags to MDX JSX components
|
|
22
|
+
remarkPlugins: [remark_rspress_map_1.remarkRspressMap],
|
|
23
|
+
// Also use rehype plugin as fallback for any remaining HTML elements
|
|
24
|
+
rehypePlugins: [rehype_rspress_map_1.rehypeRspressMap]
|
|
25
|
+
},
|
|
26
|
+
// Add runtime module to handle component mapping
|
|
27
|
+
addRuntimeModules() {
|
|
28
|
+
return {
|
|
29
|
+
'virtual:rspress-map-runtime': `
|
|
30
|
+
// Runtime module for rspress-map component
|
|
31
|
+
// This ensures the component is available globally
|
|
32
|
+
export { default as RspressMap } from '${path_1.default.join(__dirname, 'components', 'RspressMap.js')}';
|
|
33
|
+
`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
exports.default = pluginMap;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rehypeRspressMap = void 0;
|
|
4
|
+
const unist_util_visit_1 = require("unist-util-visit");
|
|
5
|
+
/**
|
|
6
|
+
* Rehype plugin to transform <rspress-map> tags to React components
|
|
7
|
+
*/
|
|
8
|
+
const rehypeRspressMap = () => {
|
|
9
|
+
return (tree) => {
|
|
10
|
+
(0, unist_util_visit_1.visit)(tree, 'element', (node) => {
|
|
11
|
+
if (node.tagName === 'rspress-map') {
|
|
12
|
+
// Transform to MDX JSX element
|
|
13
|
+
const mdxNode = {
|
|
14
|
+
type: 'mdxJsxFlowElement',
|
|
15
|
+
name: 'RspressMap',
|
|
16
|
+
attributes: [],
|
|
17
|
+
children: node.children || []
|
|
18
|
+
};
|
|
19
|
+
// Convert HTML attributes to JSX attributes
|
|
20
|
+
if (node.properties) {
|
|
21
|
+
for (const [key, value] of Object.entries(node.properties)) {
|
|
22
|
+
// Convert kebab-case to camelCase for React props
|
|
23
|
+
// marker-text -> markerText
|
|
24
|
+
const jsxKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
25
|
+
const attr = {
|
|
26
|
+
type: 'mdxJsxAttribute',
|
|
27
|
+
name: jsxKey
|
|
28
|
+
};
|
|
29
|
+
// Set value based on type
|
|
30
|
+
// For MDX, string values should be wrapped in quotes
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
attr.value = { type: 'mdxJsxAttributeValueExpression', value: JSON.stringify(value) };
|
|
33
|
+
}
|
|
34
|
+
else if (typeof value === 'boolean') {
|
|
35
|
+
attr.value = { type: 'mdxJsxAttributeValueExpression', value: String(value) };
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
attr.value = { type: 'mdxJsxAttributeValueExpression', value: JSON.stringify(value) };
|
|
39
|
+
}
|
|
40
|
+
mdxNode.attributes.push(attr);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Replace the node
|
|
44
|
+
Object.assign(node, mdxNode);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
exports.rehypeRspressMap = rehypeRspressMap;
|
|
50
|
+
exports.default = exports.rehypeRspressMap;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'unified';
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
/**
|
|
4
|
+
* Remark plugin to transform <rspress-map> HTML tags to MDX JSX components
|
|
5
|
+
* This runs before rehype, so we can transform HTML elements in markdown
|
|
6
|
+
*/
|
|
7
|
+
export declare const remarkRspressMap: Plugin<[], Root>;
|
|
8
|
+
export default remarkRspressMap;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.remarkRspressMap = void 0;
|
|
4
|
+
const unist_util_visit_1 = require("unist-util-visit");
|
|
5
|
+
/**
|
|
6
|
+
* Remark plugin to transform <rspress-map> HTML tags to MDX JSX components
|
|
7
|
+
* This runs before rehype, so we can transform HTML elements in markdown
|
|
8
|
+
*/
|
|
9
|
+
const remarkRspressMap = () => {
|
|
10
|
+
return (tree) => {
|
|
11
|
+
(0, unist_util_visit_1.visit)(tree, ['html', 'mdxJsxFlowElement', 'mdxJsxTextElement'], (node, index, parent) => {
|
|
12
|
+
// Handle HTML nodes
|
|
13
|
+
if (node.type === 'html') {
|
|
14
|
+
const htmlContent = node.value;
|
|
15
|
+
// Debug: log all HTML nodes to see what we're processing
|
|
16
|
+
if (htmlContent && htmlContent.includes('rspress-map')) {
|
|
17
|
+
console.log('[remarkRspressMap] Found literal HTML rspress-map tag:', htmlContent);
|
|
18
|
+
}
|
|
19
|
+
// Match <rspress-map> tags - handle both self-closing and with closing tag
|
|
20
|
+
// Pattern: <rspress-map ... /> or <rspress-map ...></rspress-map>
|
|
21
|
+
const tagRegex = /<rspress-map\s+([^>]*?)(?:\s*\/>|>)/;
|
|
22
|
+
const match = htmlContent.match(tagRegex);
|
|
23
|
+
if (match) {
|
|
24
|
+
console.log('[remarkRspressMap] Matched HTML tag, transforming...');
|
|
25
|
+
const attrsString = match[1];
|
|
26
|
+
const attrs = [];
|
|
27
|
+
// Parse attributes: key="value" or key='value'
|
|
28
|
+
const attrRegex = /(\w+(?:-\w+)*)\s*=\s*["']([^"']*)["']/g;
|
|
29
|
+
let attrMatch;
|
|
30
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
31
|
+
const key = attrMatch[1];
|
|
32
|
+
const value = attrMatch[2];
|
|
33
|
+
// Convert kebab-case to camelCase: marker-text -> markerText
|
|
34
|
+
const jsxKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
35
|
+
attrs.push({ name: jsxKey, value });
|
|
36
|
+
}
|
|
37
|
+
// Create MDX JSX flow element
|
|
38
|
+
const mdxNode = {
|
|
39
|
+
type: 'mdxJsxFlowElement',
|
|
40
|
+
name: 'RspressMap',
|
|
41
|
+
attributes: attrs.map(attr => ({
|
|
42
|
+
type: 'mdxJsxAttribute',
|
|
43
|
+
name: attr.name,
|
|
44
|
+
value: attr.value
|
|
45
|
+
})),
|
|
46
|
+
children: []
|
|
47
|
+
};
|
|
48
|
+
// Replace the HTML node with the MDX JSX node
|
|
49
|
+
if (parent && typeof index === 'number') {
|
|
50
|
+
parent.children[index] = mdxNode;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Handle natively parsed MDX JSX elements
|
|
55
|
+
else if ((node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') && node.name === 'rspress-map') {
|
|
56
|
+
console.log('[remarkRspressMap] Matched mdx tag, transforming name to RspressMap...');
|
|
57
|
+
// Change the element name to match the exported React component
|
|
58
|
+
node.name = 'RspressMap';
|
|
59
|
+
// Convert existing attribute names from kebab-case to camelCase
|
|
60
|
+
if (node.attributes && Array.isArray(node.attributes)) {
|
|
61
|
+
node.attributes.forEach((attr) => {
|
|
62
|
+
if (attr.type === 'mdxJsxAttribute' && typeof attr.name === 'string') {
|
|
63
|
+
attr.name = attr.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
exports.remarkRspressMap = remarkRspressMap;
|
|
72
|
+
exports.default = exports.remarkRspressMap;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rspress-plugin-map",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rspress plugin for embedding interactive maps",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"clean": "rm -rf dist",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"rspress",
|
|
17
|
+
"plugin",
|
|
18
|
+
"map",
|
|
19
|
+
"google-maps",
|
|
20
|
+
"amap",
|
|
21
|
+
"baidu-maps",
|
|
22
|
+
"openstreetmap",
|
|
23
|
+
"leaflet"
|
|
24
|
+
],
|
|
25
|
+
"author": "buyfakett <work@tteam.icu>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@rspress/core": "^2.0.0",
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
30
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rspress/core": "^2.0.3",
|
|
34
|
+
"@types/react": "^19.2.14",
|
|
35
|
+
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|