@dxpos/markdown 0.0.1 → 0.0.2
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,15 @@
|
|
|
1
|
+
import { type FC } from "react";
|
|
2
|
+
export type PreviewImage = {
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
};
|
|
6
|
+
interface ImageLightboxProps {
|
|
7
|
+
images: PreviewImage[];
|
|
8
|
+
index: number;
|
|
9
|
+
onIndexChange: (index: number) => void;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}
|
|
12
|
+
/** 全屏查看预览中的图片,并在文档顺序内切换 */
|
|
13
|
+
export declare const ImageLightbox: FC<ImageLightboxProps>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/view/image-lightbox/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAG3C,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAmBF,UAAU,kBAAkB;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,2BAA2B;AAC3B,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA4EhD,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
6
|
+
const keyHandlers = new Set();
|
|
7
|
+
// 模块加载时挂到 window 捕获阶段,早于页面弹窗的 Esc 监听
|
|
8
|
+
if (typeof window !== "undefined") {
|
|
9
|
+
window.addEventListener("keydown", (event) => {
|
|
10
|
+
for (const handler of keyHandlers) {
|
|
11
|
+
handler(event);
|
|
12
|
+
}
|
|
13
|
+
}, true);
|
|
14
|
+
}
|
|
15
|
+
/** 全屏查看预览中的图片,并在文档顺序内切换 */
|
|
16
|
+
export const ImageLightbox = ({ images, index, onIndexChange, onClose }) => {
|
|
17
|
+
const current = images[index];
|
|
18
|
+
const showNav = images.length > 1;
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const previous = document.body.style.overflow;
|
|
21
|
+
document.body.style.overflow = "hidden";
|
|
22
|
+
return () => {
|
|
23
|
+
document.body.style.overflow = previous;
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const onKeyDown = (event) => {
|
|
28
|
+
if (event.key !== "Escape" && event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
event.stopImmediatePropagation();
|
|
33
|
+
if (event.key === "Escape") {
|
|
34
|
+
onClose();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (event.key === "ArrowLeft" && index > 0) {
|
|
38
|
+
onIndexChange(index - 1);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (event.key === "ArrowRight" && index < images.length - 1) {
|
|
42
|
+
onIndexChange(index + 1);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
keyHandlers.add(onKeyDown);
|
|
46
|
+
return () => {
|
|
47
|
+
keyHandlers.delete(onKeyDown);
|
|
48
|
+
};
|
|
49
|
+
}, [images.length, index, onClose, onIndexChange]);
|
|
50
|
+
if (!current) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return createPortal(_jsxs("div", { className: "wsmd-image-lightbox", role: "dialog", "aria-modal": "true", "aria-label": "\u56FE\u7247\u9884\u89C8", children: [_jsx("button", { className: "wsmd-image-lightbox__backdrop", type: "button", "aria-label": "\u5173\u95ED\u9884\u89C8", onClick: onClose }), _jsx("button", { className: "wsmd-image-lightbox__close", type: "button", "aria-label": "\u5173\u95ED", onClick: onClose, children: _jsx(X, {}) }), showNav ? (_jsx("button", { className: "wsmd-image-lightbox__nav wsmd-image-lightbox__nav--prev", type: "button", "aria-label": "\u4E0A\u4E00\u5F20", disabled: index === 0, onClick: () => onIndexChange(index - 1), children: _jsx(ChevronLeft, {}) })) : null, _jsx("img", { className: "wsmd-image-lightbox__image", src: current.src, alt: current.alt }), showNav ? (_jsx("button", { className: "wsmd-image-lightbox__nav wsmd-image-lightbox__nav--next", type: "button", "aria-label": "\u4E0B\u4E00\u5F20", disabled: index === images.length - 1, onClick: () => onIndexChange(index + 1), children: _jsx(ChevronRight, {}) })) : null, _jsxs("p", { className: "wsmd-image-lightbox__counter", children: [index + 1, " / ", images.length] })] }), document.body);
|
|
54
|
+
};
|
package/dist/view/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/view/index.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/view/index.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2BD,eAAO,MAAM,MAAM,wGAkDjB,CAAC"}
|
package/dist/view/index.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef } from "react";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useState } from "react";
|
|
4
4
|
import Markdown from "react-markdown";
|
|
5
|
+
import { ImageLightbox } from "./image-lightbox";
|
|
6
|
+
const previewSrc = (image) => {
|
|
7
|
+
if (!image.getAttribute("src")) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
return image.currentSrc || image.src;
|
|
11
|
+
};
|
|
12
|
+
const previewImages = (root) => {
|
|
13
|
+
return Array.from(root.querySelectorAll("img")).flatMap((node) => {
|
|
14
|
+
if (!(node instanceof HTMLImageElement) || node.closest("a")) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const src = previewSrc(node);
|
|
18
|
+
if (!src) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return [{ node, image: { src, alt: node.alt } }];
|
|
22
|
+
});
|
|
23
|
+
};
|
|
5
24
|
export const Viewer = forwardRef(({ content, plugins = [], className }, ref) => {
|
|
25
|
+
const [gallery, setGallery] = useState(null);
|
|
6
26
|
const remarkPlugins = [];
|
|
7
27
|
const rehypePlugins = [];
|
|
8
28
|
for (const plugin of plugins) {
|
|
@@ -13,6 +33,25 @@ export const Viewer = forwardRef(({ content, plugins = [], className }, ref) =>
|
|
|
13
33
|
rehypePlugins.push(plugin.rehype);
|
|
14
34
|
}
|
|
15
35
|
}
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setGallery(null);
|
|
38
|
+
}, [content]);
|
|
39
|
+
const onClick = (event) => {
|
|
40
|
+
const target = event.target;
|
|
41
|
+
if (!(target instanceof Element)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const image = target.closest("img");
|
|
45
|
+
if (!(image instanceof HTMLImageElement) || !event.currentTarget.contains(image) || image.closest("a")) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const images = previewImages(event.currentTarget);
|
|
49
|
+
const index = images.findIndex((item) => item.node === image);
|
|
50
|
+
if (index < 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setGallery({ images: images.map((item) => item.image), index });
|
|
54
|
+
};
|
|
16
55
|
const rootClass = className ? `wsmd-markdown-body ${className}` : "wsmd-markdown-body";
|
|
17
|
-
return (
|
|
56
|
+
return (_jsxs("div", { ref: ref, className: rootClass, onClick: onClick, children: [_jsx(Markdown, { remarkPlugins: remarkPlugins, rehypePlugins: rehypePlugins, children: content }), gallery ? (_jsx(ImageLightbox, { images: gallery.images, index: gallery.index, onIndexChange: (index) => setGallery({ images: gallery.images, index }), onClose: () => setGallery(null) })) : null] }));
|
|
18
57
|
});
|
package/package.json
CHANGED
package/styles.css
CHANGED
|
@@ -303,6 +303,105 @@
|
|
|
303
303
|
border-radius: 8px;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
.wsmd-markdown-body img {
|
|
307
|
+
cursor: zoom-in;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.wsmd-markdown-body a img,
|
|
311
|
+
.wsmd-tiptap img {
|
|
312
|
+
cursor: auto;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.wsmd-image-lightbox {
|
|
316
|
+
position: fixed;
|
|
317
|
+
inset: 0;
|
|
318
|
+
z-index: 1000;
|
|
319
|
+
display: flex;
|
|
320
|
+
align-items: center;
|
|
321
|
+
justify-content: center;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.wsmd-image-lightbox__backdrop {
|
|
325
|
+
position: absolute;
|
|
326
|
+
inset: 0;
|
|
327
|
+
border: 0;
|
|
328
|
+
padding: 0;
|
|
329
|
+
background: rgb(0 0 0 / 72%);
|
|
330
|
+
cursor: zoom-out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.wsmd-image-lightbox__image {
|
|
334
|
+
position: relative;
|
|
335
|
+
z-index: 1;
|
|
336
|
+
max-width: calc(100% - 128px);
|
|
337
|
+
max-height: calc(100% - 96px);
|
|
338
|
+
object-fit: contain;
|
|
339
|
+
border-radius: 8px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.wsmd-image-lightbox__close,
|
|
343
|
+
.wsmd-image-lightbox__nav {
|
|
344
|
+
position: absolute;
|
|
345
|
+
z-index: 2;
|
|
346
|
+
display: inline-flex;
|
|
347
|
+
align-items: center;
|
|
348
|
+
justify-content: center;
|
|
349
|
+
width: 40px;
|
|
350
|
+
height: 40px;
|
|
351
|
+
padding: 0;
|
|
352
|
+
border: 0;
|
|
353
|
+
border-radius: 999px;
|
|
354
|
+
background: rgb(0 0 0 / 45%);
|
|
355
|
+
color: #fff;
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.wsmd-image-lightbox__close:hover,
|
|
360
|
+
.wsmd-image-lightbox__nav:hover:not(:disabled) {
|
|
361
|
+
background: rgb(255 255 255 / 20%);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.wsmd-image-lightbox__nav:disabled {
|
|
365
|
+
opacity: 0.35;
|
|
366
|
+
cursor: not-allowed;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.wsmd-image-lightbox__close svg,
|
|
370
|
+
.wsmd-image-lightbox__nav svg {
|
|
371
|
+
width: 20px;
|
|
372
|
+
height: 20px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.wsmd-image-lightbox__close {
|
|
376
|
+
top: 16px;
|
|
377
|
+
right: 16px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.wsmd-image-lightbox__nav {
|
|
381
|
+
top: 50%;
|
|
382
|
+
transform: translateY(-50%);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.wsmd-image-lightbox__nav--prev {
|
|
386
|
+
left: 16px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.wsmd-image-lightbox__nav--next {
|
|
390
|
+
right: 16px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.wsmd-image-lightbox__counter {
|
|
394
|
+
position: absolute;
|
|
395
|
+
bottom: 16px;
|
|
396
|
+
left: 50%;
|
|
397
|
+
z-index: 2;
|
|
398
|
+
margin: 0;
|
|
399
|
+
transform: translateX(-50%);
|
|
400
|
+
color: #fff;
|
|
401
|
+
font-family: var(--wsmd-font-mono), ui-monospace, monospace;
|
|
402
|
+
font-size: 13px;
|
|
403
|
+
}
|
|
404
|
+
|
|
306
405
|
.wsmd-markdown-editor__loading {
|
|
307
406
|
padding: 20px 24px;
|
|
308
407
|
color: var(--wsmd-muted);
|