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-link.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 * as CodeMirror from "codemirror";
7
import { Button, Checkbox, Form, Input, Modal } from "antd";
8
9
import { show_react_modal } from "../../misc";
10
import { Icon } from "../../components";
11
import { alert_message } from "../../alerts";
12
13
export interface Options {
14
url: string;
15
displayed_text: string;
16
target: boolean; // if true, opens in a new window
17
title: string;
18
}
19
20
function insert_link(mode: string, opts: Options): string {
21
for (const k in opts) {
22
if (typeof opts[k] == "string") {
23
opts[k] = opts[k].trim();
24
}
25
}
26
let { url, displayed_text, target, title } = opts;
27
let s: string = "";
28
if (mode === "md") {
29
// [Python](http://www.python.org/ "the python website")
30
if (title.length > 0) {
31
title = ` \"${title}\"`;
32
}
33
if (displayed_text.length > 0) {
34
s = `[${displayed_text}](${url}${title})`;
35
} else {
36
s = url;
37
}
38
} else if (mode === "rst") {
39
// `Python <http://www.python.org/#target>`_
40
if (displayed_text.length == 0) {
41
displayed_text = url;
42
}
43
s = `\`${displayed_text} <${url}>\`_`;
44
} else if (mode === "tex") {
45
// \url{http://www.wikibooks.org}
46
// \href{http://www.wikibooks.org}{Wikibooks home}
47
url = url.replace(/#/g, "\\#"); // should end up as \#
48
url = url.replace(/&/g, "\\&"); // ... \&
49
url = url.replace(/_/g, "\\_"); // ... \_
50
if (displayed_text.length > 0) {
51
s = `\\href{${url}}{${displayed_text}}`;
52
} else {
53
s = `\\url{${url}}`;
54
}
55
} else if (mode === "mediawiki") {
56
// https://www.mediawiki.org/wiki/Help:Links
57
// [http://mediawiki.org MediaWiki]
58
if (displayed_text.length > 0) {
59
displayed_text = ` ${displayed_text}`;
60
}
61
s = `[${url}${displayed_text}]`;
62
} else {
63
// if (mode == "html") ## HTML default fallback
64
const target1 = target ? " target='_blank' rel='noopener'" : "";
65
66
if (title.length > 0) {
67
title = ` title='${title}'`;
68
}
69
70
if (displayed_text.length == 0) {
71
displayed_text = url;
72
}
73
s = `<a href='${url}'${title}${target1}>${displayed_text}</a>`;
74
}
75
return s;
76
}
77
78
export async function get_insert_link_opts_from_user(
79
default_display: string,
80
show_target: boolean
81
): Promise<undefined | Options> {
82
return await show_react_modal((cb) => {
83
return (
84
<Modal
85
title={
86
<h3>
87
<Icon name="link" /> Insert Link
88
</h3>
89
}
90
open
91
footer={<Button onClick={() => cb()}>Cancel</Button>}
92
onCancel={() => cb()}
93
>
94
<Form
95
labelCol={{ span: 8 }}
96
wrapperCol={{ span: 16 }}
97
name="options"
98
initialValues={{
99
url: "",
100
displayed_text: default_display,
101
target: false,
102
title: "",
103
}}
104
onFinish={(values) => {
105
// empty displayed text really doesn't work well (since can't see the link).
106
if (!values.displayed_text) values.displayed_text = values.title;
107
if (!values.displayed_text) values.displayed_text = values.url;
108
if (!values.displayed_text) values.displayed_text = "link";
109
cb(undefined, values);
110
}}
111
onFinishFailed={(err) => cb(err)}
112
>
113
<Form.Item
114
label="URL"
115
name="url"
116
rules={[
117
{
118
required: true,
119
message: "You must enter the URL of the link.",
120
},
121
]}
122
>
123
<Input placeholder="URL..." />
124
</Form.Item>
125
<Form.Item label="Displayed text" name="displayed_text">
126
<Input placeholder="Displayed text..." />
127
</Form.Item>
128
<Form.Item label="Title" name="title">
129
<Input placeholder="Title..." />
130
</Form.Item>
131
{show_target && (
132
<Form.Item label="Target" name="target" valuePropName="checked">
133
<Checkbox>Open in new window</Checkbox>
134
</Form.Item>
135
)}
136
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
137
<Button type="primary" htmlType="submit">
138
Submit
139
</Button>
140
</Form.Item>
141
</Form>
142
</Modal>
143
);
144
});
145
}
146
147
CodeMirror.defineExtension("insert_link", async function () {
148
// @ts-ignore
149
const cm = this;
150
151
const default_display = cm.getSelection();
152
const mode = cm.get_edit_mode();
153
154
// HTML target option not supported for md, rst, and tex, which have
155
// their own notation for links (and always open externally).
156
const show_target = ["md", "rst", "tex"].indexOf(mode) == -1;
157
158
let opts: Options | undefined = undefined;
159
try {
160
opts = await get_insert_link_opts_from_user(default_display, show_target);
161
} catch (err) {
162
alert_message({ type: "error", message: err.errorFields[0]?.errors });
163
return;
164
}
165
166
if (opts == null) {
167
return; // user canceled
168
}
169
170
const selections = cm.listSelections();
171
selections.reverse();
172
for (const sel of selections) {
173
const link = insert_link(cm.get_edit_mode(sel.head), opts);
174
if (sel.empty()) {
175
cm.replaceRange(link, sel.head);
176
} else {
177
cm.replaceRange(link, sel.from(), sel.to());
178
}
179
}
180
});
181
182