Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/elements/image/editable.tsx
1698 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { useEffect, useRef, useState } from "react";
7
import { register } from "../register";
8
import { useFocused, useProcessLinks, useSelected, useSlate } from "../hooks";
9
import { useSetElement } from "../set-element";
10
import { FOCUSED_COLOR } from "../../util";
11
import { Resizable } from "re-resizable";
12
import { Image } from "./index";
13
import { useFileContext } from "@cocalc/frontend/lib/file-context";
14
15
// NOTE: We do NOT use maxWidth:100% since that ends up
16
// making the resizing screw up the aspect ratio.
17
18
function toFloat(s: number | string | undefined): number | undefined {
19
if (s == null) return s;
20
if (typeof s == "string" && s.endsWith("%")) return undefined;
21
return typeof s == "number" ? s : parseFloat(s);
22
}
23
24
register({
25
slateType: "image",
26
27
fromSlate: ({ node }) => {
28
// ![ALT](https://wstein.org/bella-and-william.jpg "title")
29
let src = node.src ?? "";
30
let alt = node.alt ?? "";
31
let title = node.title ?? "";
32
let width = node.width;
33
let height = node.height;
34
if (!width && !height && !src.match(/\s/)) {
35
// no width, no height and src has no spaces in it!
36
// Our markdown processes doesn't work when the
37
// image url has a space (or at least it is a pain
38
// to escape it), and using quotes doesn't easily
39
// workaround that so we just use an img take when
40
// src has whitespace (below).
41
if (title.length > 0) {
42
title = ` \"${title}\"`;
43
}
44
return `![${alt}](${src}${title})`;
45
} else {
46
// width or height require using html instead, unfortunately...
47
width = width ? ` width="${width}"` : "";
48
height = height ? ` height="${height}"` : "";
49
title = title ? ` title="${title}"` : "";
50
src = src ? `src="${src}"` : "";
51
alt = alt ? ` alt="${alt}"` : "";
52
// Important: this **must** start with '<img ' right now
53
// due to our fairly naive parsing code for html blocks.
54
//
55
// Also, object-fit: allows us to specify the
56
// width and height and have a general CSS max-width,
57
// without screwing up the aspect ratio.
58
return `<img ${src} ${alt} ${width} ${height} ${title} style="object-fit:cover"/>`;
59
}
60
},
61
62
Element: ({ attributes, children, element }) => {
63
const node = element as Image;
64
const { src, alt, title } = node;
65
66
const [width, setWidth] = useState<number | undefined>(toFloat(node.width));
67
const [height, setHeight] = useState<number | undefined>(
68
toFloat(node.height)
69
);
70
71
useEffect(() => {
72
if (node.width && width != toFloat(node.width)) {
73
setWidth(toFloat(node.width));
74
}
75
if (node.height && height != toFloat(node.height)) {
76
setHeight(toFloat(node.height));
77
}
78
}, [element]);
79
80
const focused = useFocused();
81
const selected = useSelected();
82
const border = `2px solid ${
83
focused && selected ? FOCUSED_COLOR : "transparent"
84
}`;
85
86
const ref = useProcessLinks([src]);
87
const imageRef = useRef<any>(null);
88
89
const editor = useSlate();
90
const setElement = useSetElement(editor, element);
91
const { urlTransform } = useFileContext();
92
93
return (
94
<span {...attributes}>
95
<span ref={ref} contentEditable={false}>
96
<Resizable
97
style={{
98
display: "inline-block",
99
border,
100
maxWidth: "100%",
101
}}
102
lockAspectRatio={true}
103
size={
104
width != null && height != null
105
? {
106
width,
107
height,
108
}
109
: undefined
110
}
111
onResizeStop={(_e, _direction, _ref, d) => {
112
if (width == null || height == null) return;
113
const new_width = width + d.width;
114
const new_height = height + d.height;
115
setElement({
116
height: `${new_height}px`,
117
width: `${new_width}px`,
118
});
119
editor.saveValue();
120
setWidth(new_width);
121
setHeight(new_height);
122
}}
123
>
124
<img
125
onLoad={() => {
126
const elt = $(imageRef.current);
127
const width = elt.width() ?? 0;
128
const height = elt.height() ?? 0;
129
setWidth(width);
130
setHeight(height);
131
}}
132
ref={imageRef}
133
src={urlTransform?.(src, 'img') ?? src}
134
alt={alt}
135
title={title}
136
style={{
137
height: height ?? node.height,
138
width: width ?? node.width,
139
maxWidth: "100%",
140
maxHeight: "100%",
141
objectFit: "cover",
142
}}
143
/>
144
</Resizable>
145
</span>
146
{children}
147
</span>
148
);
149
},
150
});
151
152