tiptap-markdown-react 1.2.1 → 1.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.
@@ -0,0 +1,50 @@
1
+ import { Editor } from '@tiptap/core';
2
+ import { ImageCrop } from './imageMarkdown';
3
+ /**
4
+ * 图片裁剪的交互几何与写入。
5
+ *
6
+ * 矩形是相对原图的比例(0–1),不是像素:显示尺寸(width / height)另算,
7
+ * 之后再拖缩放不会把裁剪区域冲掉。原图 `src` 保持不变,所以裁剪可重入、可重置。
8
+ */
9
+ /** 裁剪框拖动手柄。 */
10
+ export type ImageCropHandle = 'n' | 's' | 'e' | 'w' | 'nw' | 'ne' | 'sw' | 'se';
11
+ export interface ImageCropMetrics {
12
+ naturalWidth: number;
13
+ naturalHeight: number;
14
+ /**
15
+ * 当前可见宽度。已经裁过的图,这是裁剪框的显示宽,不是整张原图的显示宽。
16
+ */
17
+ displayWidth: number;
18
+ }
19
+ /** 平移裁剪框,夹在原图内(尺寸不变)。 */
20
+ export declare function moveCrop(start: ImageCrop, dx: number, dy: number): ImageCrop;
21
+ /**
22
+ * 按手柄缩放裁剪框。被拖的是对应的边,对边钉住;拖过对边时那一侧改由最小边长顶住。
23
+ */
24
+ export declare function resizeCrop(start: ImageCrop, handle: ImageCropHandle, dx: number, dy: number): ImageCrop;
25
+ /**
26
+ * 裁剪前后保持「原图像素 → 屏幕像素」的比例,算出新的可见宽高。
27
+ *
28
+ * `from == null` 表示当前看见的是整张图。图片还没加载出自然尺寸时返回 null,
29
+ * 调用方只写 crop、不动 width / height。
30
+ */
31
+ export declare function croppedDisplaySize(args: {
32
+ naturalWidth: number;
33
+ naturalHeight: number;
34
+ displayWidth: number;
35
+ from: ImageCrop | null;
36
+ to: ImageCrop | null;
37
+ }): {
38
+ width: number;
39
+ height: number;
40
+ } | null;
41
+ /**
42
+ * 按文档位置写入裁剪。`crop == null` 清除。
43
+ *
44
+ * 同时按 {@link croppedDisplaySize} 改 width / height,让可见区域立刻变成裁剪结果,
45
+ * 而不是把整张图塞进原来的盒子里。一次事务,撤销一步回到裁之前。
46
+ * 返回是否找到图片。没变化时不写事务。
47
+ */
48
+ export declare function setImageCropAtPos(editor: Editor, pos: number, crop: ImageCrop | null, metrics?: ImageCropMetrics | null): boolean;
49
+ /** 设置「当前图片」(选中 / 光标紧邻)的裁剪。返回是否找到图片。 */
50
+ export declare function setImageCrop(editor: Editor, crop: ImageCrop | null, metrics?: ImageCropMetrics | null): boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { ImageCrop } from './imageMarkdown';
2
+ /**
3
+ * 编辑态裁剪的 DOM 同步。
4
+ *
5
+ * 官方 ResizableNodeView 把 width / height 写在它的 `element` 上,并按这个元素的
6
+ * offsetWidth 计算拖拽。裁剪之后用户看见、拖到的应该是裁剪框,不是整张原图,
7
+ * 所以裁过的图在 img 外包一层 frame,把 `element` 换成 frame:拖拽改的是可见
8
+ * 尺寸,img 用百分比铺进裁剪窗口(原图比例不变)。没裁剪时原样把尺寸写回 img,
9
+ * 不插入 frame,缩放路径与官方一致。
10
+ */
11
+ interface ResizeHost {
12
+ element: HTMLElement;
13
+ onResize?: (width: number, height: number) => void;
14
+ }
15
+ /**
16
+ * 把节点 attrs 同步到这张图的 DOM。`host` 是官方 ResizableNodeView
17
+ * (裁剪时把它的 element / onResize 临时换成 frame)。
18
+ */
19
+ export declare function syncResizableImageCrop(host: ResizeHost, img: HTMLImageElement, attrs: Record<string, unknown>): void;
20
+ export interface CropSurface {
21
+ /** 盖在文档原图上的层,选区和手柄画在这里。 */
22
+ overlay: HTMLDivElement;
23
+ /** 展开原图之前的可见宽度,用来在提交时保持缩放比例。 */
24
+ displayWidth: number;
25
+ /** 丢掉展开状态,交给随后的 attrs 同步写回裁剪结果。 */
26
+ discard: () => void;
27
+ /** 取消:把 DOM 收回打开裁剪之前。 */
28
+ restore: () => void;
29
+ }
30
+ /**
31
+ * 在文档里的这张图上开始裁剪。
32
+ *
33
+ * 已经裁过的图按「重置」后的样子铺开:整张原图从图片块的左上角开始,比例和重置
34
+ * 一样,选区再盖回当前裁剪。不改块的左上角,也不把原图用负偏移甩到裁剪框外面。
35
+ * 没裁过则遮罩直接盖住当前图片。
36
+ */
37
+ export declare function openCropSurface(img: HTMLImageElement, crop: ImageCrop | null): CropSurface;
38
+ export {};
@@ -3,14 +3,16 @@ import { DOMOutputSpec } from '@tiptap/pm/model';
3
3
  /**
4
4
  * 图片节点的 markdown 往返(客户端 / 预览 / server 三端共用)。
5
5
  *
6
- * 尺寸(拖拽缩放的产物,整数像素)、对齐(`data-align`)与描述(图注)在 markdown 里
7
- * 都没有原生语法,HTML 是唯一能带上它们的 CommonMark 合法形式:
8
- * `<img src="…" alt="…" width="300" data-align="center">`,
6
+ * 尺寸(拖拽缩放的产物,整数像素)、对齐(`data-align`)、裁剪(`data-crop`)与描述
7
+ * (图注)在 markdown 里都没有原生语法,HTML 是唯一能带上它们的 CommonMark 合法形式:
8
+ * `<img src="…" alt="…" width="300" data-align="center" data-crop="0.1000,0.0000,0.5000,1.0000">`,
9
9
  * 带描述时再包一层 `<figure><img …><figcaption>…</figcaption></figure>`(HTML 标准语义,
10
10
  * GitHub 同样认)。因此:
11
11
  *
12
- * - 序列化:**只有带尺寸 / 对齐 / 描述的图**输出 HTML 形式;没动过的图保持标准
12
+ * - 序列化:**只有带尺寸 / 对齐 / 裁剪 / 描述的图**输出 HTML 形式;没动过的图保持标准
13
13
  * `![alt](src "title")`,markdown 不为没用过的功能付可读性代价。
14
+ * - 裁剪不改 `src`(原图还在)。`data-crop` 是相对原图的 `x,y,w,h`(0–1,四位小数);
15
+ * HTML 里再用一层 `.tmr-image-crop` 把可见区域裁出来,编辑态 / 预览 / SSR 同一套。
14
16
  * - 解析:自建 block 级 tokenizer 认领 `<img …>` 与 `<figure>…</figure>`(一个扩展
15
17
  * 只能挂一个 tokenizer)。不认领的话,客户端靠 `window.DOMParser` 隐式还原、
16
18
  * server 端没有 window 会把它转义成字面文本(与 sup/sub 当初踩的是同一条边界)。
@@ -25,6 +27,16 @@ export interface ImageSizeAttrs {
25
27
  }
26
28
  /** 图片对齐(HTML 属性 `data-align`,与官方 image-align-button 同名)。 */
27
29
  export type ImageAlign = 'left' | 'center' | 'right';
30
+ /**
31
+ * 裁剪矩形,相对原图宽高的比例(0–1)。
32
+ * `x` / `y` 是左上角,`w` / `h` 是宽高。整张图(或几乎整张)归一成 null,不进 markdown。
33
+ */
34
+ export interface ImageCrop {
35
+ x: number;
36
+ y: number;
37
+ w: number;
38
+ h: number;
39
+ }
28
40
  /** 归一化对齐值:只认 left / center / right,其余(含空、任意脏值)为 null。 */
29
41
  export declare function normalizeImageAlign(value: unknown): ImageAlign | null;
30
42
  /**
@@ -42,9 +54,36 @@ export declare const imageAlignAttribute: {
42
54
  };
43
55
  };
44
56
  /**
45
- * `caption`(图注)属性的共享配置:不进 img 标签(是节点级 renderHTML 包的
46
- * `<figure>/<figcaption>` 结构),解析侧从 figure 里读。
57
+ * 归一化裁剪:只认落在原图内、边长够得着的矩形。
58
+ * 整张图、空值、脏字符串(`data-crop="nope"`)都是 null。
59
+ */
60
+ export declare function normalizeImageCrop(value: unknown): ImageCrop | null;
61
+ /** `data-crop` 的规范写法(四位小数,方便往返幂等)。 */
62
+ export declare function formatImageCrop(crop: ImageCrop): string;
63
+ /** img 在裁剪框里的百分比定位(框是可见区域,img 铺开整张原图)。 */
64
+ export declare function cropImageBox(crop: ImageCrop): {
65
+ width: string;
66
+ height: string;
67
+ left: string;
68
+ top: string;
69
+ };
70
+ /** 静态 HTML(预览 / SSR)里 img 的内联样式,与编辑态 frame 里的 img 同一套数字。 */
71
+ export declare function cropImageCss(crop: ImageCrop): string;
72
+ /** 裁剪框的布局尺寸:宽是可见像素,高交给 aspect-ratio,窄屏 max-width:100% 时还能保持比例。 */
73
+ export declare function cropFrameCss(width: number, height: number): string;
74
+ /**
75
+ * `crop` 属性的共享配置:attrs 里是 {@link ImageCrop},HTML 渲染成 `data-crop`。
76
+ * 视觉裁剪不在这个属性上(img 的 style / 外包 frame 由 imageNodeHtml 与编辑态 NodeView 写)。
47
77
  */
78
+ export declare const imageCropAttribute: {
79
+ default: null;
80
+ parseHTML: (element: HTMLElement) => ImageCrop | null;
81
+ renderHTML: (attributes: Record<string, unknown>) => {
82
+ 'data-crop': string;
83
+ } | {
84
+ 'data-crop'?: undefined;
85
+ };
86
+ };
48
87
  export declare const imageCaptionAttribute: {
49
88
  default: null;
50
89
  parseHTML: (element: HTMLElement) => string | null;
@@ -52,7 +91,8 @@ export declare const imageCaptionAttribute: {
52
91
  };
53
92
  /**
54
93
  * 节点级 HTML 渲染:有描述时输出 `<figure><img …><figcaption>…</figcaption></figure>`
55
- * (HTML 标准语义),否则维持官方单 `<img>` 结构。两份扩展共用。
94
+ * (HTML 标准语义),否则维持官方单 `<img>` 结构。有裁剪时 img 外包一层裁剪框。
95
+ * 两份扩展共用。
56
96
  */
57
97
  export declare function imageNodeHtml(output: {
58
98
  attrs: Record<string, unknown>;
@@ -69,7 +109,7 @@ export declare function imageFigureParseRule(): {
69
109
  /** 图片描述(图注)归一化:去掉首尾空白,空串归 null。 */
70
110
  export declare function normalizeImageCaption(value: unknown): string | null;
71
111
  /**
72
- * 图片的 markdown 序列化:带尺寸 / 对齐 / 描述的输出 HTML 形式,否则标准语法。
112
+ * 图片的 markdown 序列化:带尺寸 / 对齐 / 裁剪 / 描述的输出 HTML 形式,否则标准语法。
73
113
  * 描述(图注)用 `<figure>` + `<figcaption>` 包裹(HTML 标准语义,GitHub 同样认);
74
114
  * 覆盖 @tiptap/extension-image 的内置实现(它只输出 src/alt/title,其余都会丢)。
75
115
  */
@@ -82,5 +122,5 @@ export declare function imageRenderMarkdown(node: {
82
122
  * `parseMarkdown` 只做搬运——与 CitationRef 的 tokenizer 同一套写法。
83
123
  */
84
124
  export declare const imageMarkdownTokenizer: MarkdownTokenizer;
85
- /** 把上面认领的 token 还原成 image 节点(含尺寸、对齐与描述)。 */
125
+ /** 把上面认领的 token 还原成 image 节点(含尺寸、对齐、裁剪与描述)。 */
86
126
  export declare function imageParseMarkdown(token: MarkdownToken, helpers: MarkdownParseHelpers): JSONContent;
@@ -0,0 +1,49 @@
1
+ import { ImageCrop } from './imageMarkdown';
2
+ /**
3
+ * 图片灯箱的纯逻辑:从正文里收集图、判断点到的是哪一张、以及适应视口的缩放。
4
+ * 阅读态单击打开,编辑态双击或工具条打开;裁剪进行时不抢手势。
5
+ */
6
+ export interface PreviewImage {
7
+ src: string;
8
+ alt: string;
9
+ crop: ImageCrop | null;
10
+ }
11
+ /** 灯箱自定义事件。工具条在 body 上,不在正文根里,用它通知所属正文打开这一张。 */
12
+ export declare const IMAGE_PREVIEW_EVENT = "tmr-image-preview";
13
+ export declare function requestImagePreview(img: HTMLImageElement): void;
14
+ /** 正文里按阅读顺序能预览的图。灯箱自己 portal 出去的图不算。 */
15
+ export declare function listPreviewImages(root: ParentNode): HTMLImageElement[];
16
+ export declare function toPreviewImage(img: HTMLImageElement): PreviewImage;
17
+ /**
18
+ * 点击 / 双击的目标是不是一张正文图。
19
+ * 裁剪进行时、缩放手柄上都不打开,避免和剪裁、缩放抢手势。
20
+ * 裁过的图 img 是 pointer-events:none,点到的是外面的 `.tmr-image-crop`。
21
+ */
22
+ export declare function previewImageFromTarget(target: EventTarget | null): HTMLImageElement | null;
23
+ /** 灯箱里要显示的像素尺寸:裁剪后是可见区域,不是整张原图。 */
24
+ export declare function previewPixelSize(naturalWidth: number, naturalHeight: number, crop: ImageCrop | null): {
25
+ width: number;
26
+ height: number;
27
+ };
28
+ export interface PreviewFit {
29
+ /** 未旋转时的布局宽(zoom = 1 时正好适应视口)。 */
30
+ width: number;
31
+ /** 未旋转时的布局高。 */
32
+ height: number;
33
+ /** 乘上它之后,屏幕上的一边等于可见区域的像素。放大缩小的上下限用它兜住。 */
34
+ actualZoom: number;
35
+ }
36
+ /**
37
+ * zoom = 1 时图适应视口(大图缩小,小图放大,和阅读页灯箱一样铺满可视区域)。
38
+ * 旋转 90/270 后按转过的外接框来适应;返回的宽高仍是旋转前的布局盒,
39
+ * 视觉上的外接框由 CSS rotate 交换。自适应就是 zoom = 1。
40
+ */
41
+ export declare function fitPreviewBox(input: {
42
+ viewportWidth: number;
43
+ viewportHeight: number;
44
+ imageWidth: number;
45
+ imageHeight: number;
46
+ rotation: number;
47
+ }): PreviewFit | null;
48
+ /** 放大、缩小、自适应(回到适应视口的 zoom = 1)。 */
49
+ export declare function nextPreviewZoom(action: 'in' | 'out' | 'fit', zoom: number, actualZoom: number): number;
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -28,7 +28,8 @@ export { stabilizeMarkdown } from './stabilizeMarkdown';
28
28
  export { resetImageSize } from './resetImageSize';
29
29
  export { isImageAlignActive, setImageAlign, setImageAlignAt, } from './imageAlign';
30
30
  export { setImageCaption, setImageCaptionAtPos } from './imageCaption';
31
- export { imageMarkdownTokenizer, imageParseMarkdown, imageRenderMarkdown, normalizeImageAlign, normalizeImageCaption, type ImageAlign, type ImageSizeAttrs, } from './imageMarkdown';
31
+ export { setImageCrop, setImageCropAtPos } from './imageCrop';
32
+ export { imageMarkdownTokenizer, imageParseMarkdown, imageRenderMarkdown, normalizeImageAlign, normalizeImageCaption, normalizeImageCrop, type ImageAlign, type ImageCrop, type ImageSizeAttrs, } from './imageMarkdown';
32
33
  export { MarkdownPaste, looksLikeMarkdown } from './markdownPaste';
33
34
  export { MarkdownFileDrop } from './markdownFileDrop';
34
35
  export { extractToc, type TocItem } from './toc/extractToc';