CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/codemirror/extensions/insert-image.tsx
Views: 687
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 { Button, Form, Input, Modal } from "antd";
7
import * as CodeMirror from "codemirror";
8
9
import { alert_message } from "@cocalc/frontend/alerts";
10
import { Icon } from "@cocalc/frontend/components";
11
import { show_react_modal } from "@cocalc/frontend/misc";
12
13
export interface Options {
14
url: string;
15
title: string;
16
height: string;
17
width: string;
18
}
19
20
function insert_image(mode: string, opts: Options): string {
21
let { url, title, height, width } = opts;
22
let s = "";
23
24
if (mode === "rst") {
25
// .. image:: picture.jpeg
26
// :height: 100px
27
// :width: 200 px
28
// :alt: alternate text
29
// :align: right
30
s = `\n.. image:: ${url}\n`;
31
if (height.length > 0) {
32
s += ` :height: ${height}px\n`;
33
}
34
if (width.length > 0) {
35
s += ` :width: ${width}px\n`;
36
}
37
if (title.length > 0) {
38
s += ` :alt: ${title}\n`;
39
}
40
} else if (mode === "md" && width.length === 0 && height.length === 0) {
41
// use markdown's funny image format if width/height not given
42
if (title.length > 0) {
43
title = ` \"${title}\"`;
44
}
45
s = `![](${url}${title})`;
46
} else if (mode === "tex") {
47
//cm.tex_ensure_preamble("\\usepackage{graphicx}");
48
const w = parseInt(width);
49
if (isNaN(w)) {
50
width = "0.8";
51
} else {
52
width = `${w / 100.0}`;
53
}
54
if (title.length > 0) {
55
s = `\
56
\\begin{figure}[p]
57
\\centering
58
\\includegraphics[width=${width}\\textwidth]{${url}}
59
\\caption{${title}}
60
\\end{figure}\
61
`;
62
} else {
63
s = `\\includegraphics[width=${width}\\textwidth]{${url}}`;
64
}
65
} else if (mode === "mediawiki") {
66
// https://www.mediawiki.org/wiki/Help:Images
67
// [[File:Example.jpg|<width>[x<height>]px]]
68
let size = "";
69
if (width.length > 0) {
70
size = `|${width}`;
71
if (height.length > 0) {
72
size += `x${height}`;
73
}
74
size += "px";
75
}
76
s = `[[File:${url}${size}]]`;
77
} else {
78
// fallback for mode == "md" but height or width is given
79
if (title.length > 0) {
80
title = ` title='${title}'`;
81
}
82
if (width.length > 0) {
83
width = ` width=${width}`;
84
}
85
if (height.length > 0) {
86
height = ` height=${height}`;
87
}
88
s = `<img src='${url}'${width}${height}${title} />`;
89
}
90
return s;
91
}
92
93
export async function get_insert_image_opts_from_user(
94
note = "",
95
): Promise<undefined | Options> {
96
const opts = await show_react_modal((cb) => {
97
return (
98
<Modal
99
title={
100
<div>
101
<h3>
102
<Icon name="image" /> Insert Image
103
</h3>
104
<div style={{ fontWeight: 300 }}>{note}</div>
105
</div>
106
}
107
open
108
footer={<Button onClick={() => cb()}>Cancel</Button>}
109
onCancel={() => cb()}
110
>
111
<Form
112
labelCol={{ span: 8 }}
113
wrapperCol={{ span: 16 }}
114
name="options"
115
initialValues={{
116
url: "",
117
width: "",
118
height: "",
119
title: "",
120
}}
121
onFinish={(values) => cb(undefined, values)}
122
onFinishFailed={(err) => cb(err)}
123
>
124
<Form.Item
125
label="URL"
126
name="url"
127
rules={[
128
{
129
required: true,
130
message: "You must enter the URL of the link.",
131
},
132
]}
133
>
134
<Input placeholder="URL..." />
135
</Form.Item>
136
<Form.Item label="Width" name="width">
137
<Input placeholder="Width..." />
138
</Form.Item>
139
<Form.Item label="Height" name="height">
140
<Input placeholder="Height..." />
141
</Form.Item>
142
<Form.Item label="Title" name="title">
143
<Input placeholder="Title..." />
144
</Form.Item>
145
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
146
<Button type="primary" htmlType="submit">
147
Submit
148
</Button>
149
</Form.Item>
150
</Form>
151
</Modal>
152
);
153
});
154
if (opts != null) {
155
// if width or height are numbers, append pixel units.
156
for (const x of ["width", "height"]) {
157
if (opts[x] && opts[x].match(/^[\.0-9]+$/)) {
158
opts[x] += "px";
159
}
160
}
161
}
162
return opts;
163
}
164
165
CodeMirror.defineExtension("insert_image", async function (): Promise<void> {
166
// @ts-ignore
167
const cm = this;
168
let opts: Options | undefined = undefined;
169
try {
170
opts = await get_insert_image_opts_from_user();
171
} catch (err) {
172
alert_message({ type: "error", message: err.errorFields[0]?.errors });
173
return;
174
}
175
176
if (opts == null) {
177
return; // user canceled
178
}
179
180
const selections = cm.listSelections();
181
selections.reverse();
182
for (const sel of selections) {
183
const link = insert_image(cm.get_edit_mode(sel.head), opts);
184
if (sel.empty()) {
185
cm.replaceRange(link, sel.head);
186
} else {
187
cm.replaceRange(link, sel.from(), sel.to());
188
}
189
}
190
});
191
192