@contentful/field-editor-markdown 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.
- package/dist/cjs/MarkdownActions.js +235 -0
- package/dist/cjs/MarkdownEditor.js +180 -0
- package/dist/cjs/__fixtures__/FakeSdk.js +183 -0
- package/dist/cjs/__fixtures__/asset/index.js +37 -0
- package/dist/cjs/__fixtures__/content-type/index.js +16 -0
- package/dist/cjs/__fixtures__/entry/index.js +33 -0
- package/dist/cjs/__fixtures__/fixtures.js +71 -0
- package/dist/cjs/__fixtures__/locale/index.js +40 -0
- package/dist/cjs/__fixtures__/space/index.js +16 -0
- package/dist/cjs/codemirrorImports.js +9 -0
- package/dist/cjs/components/HeadingSelector.js +66 -0
- package/dist/cjs/components/InsertLinkSelector.js +86 -0
- package/dist/cjs/components/MarkdownBottomBar.js +111 -0
- package/dist/cjs/components/MarkdownConstraints.js +79 -0
- package/dist/cjs/components/MarkdownPreview.js +249 -0
- package/dist/cjs/components/MarkdownTabs.js +128 -0
- package/dist/cjs/components/MarkdownTextarea/CodeMirrorWrapper.js +383 -0
- package/dist/cjs/components/MarkdownTextarea/MarkdownCommands.js +233 -0
- package/dist/cjs/components/MarkdownTextarea/MarkdownTextarea.js +190 -0
- package/dist/cjs/components/MarkdownTextarea/createMarkdownEditor.js +101 -0
- package/dist/cjs/components/MarkdownToolbar.js +367 -0
- package/dist/cjs/components/icons.js +193 -0
- package/dist/cjs/dialogs/CheatsheetModalDialog.js +239 -0
- package/dist/cjs/dialogs/ConfirmInsertAssetModalDialog.js +94 -0
- package/dist/cjs/dialogs/EmdebExternalContentDialog.js +202 -0
- package/dist/cjs/dialogs/InsertLinkModalDialog.js +149 -0
- package/dist/cjs/dialogs/InsertTableModalDialog.js +140 -0
- package/dist/cjs/dialogs/SpecialCharacterModalDialog.js +146 -0
- package/dist/cjs/dialogs/ZenModeModalDialog.js +257 -0
- package/dist/cjs/dialogs/openMarkdownDialog.js +121 -0
- package/dist/cjs/dialogs/renderMarkdownDialog.js +108 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/types.js +20 -0
- package/dist/cjs/utils/insertAssetLinks.js +122 -0
- package/dist/cjs/utils/insertAssetLinks.spec.js +22 -0
- package/dist/cjs/utils/isValidUrl.js +22 -0
- package/dist/cjs/utils/linkOrganizer.js +187 -0
- package/dist/cjs/utils/linkOrganizer.spec.js +96 -0
- package/dist/cjs/utils/replaceMailtoAmp.js +15 -0
- package/dist/cjs/utils/replaceMailtoAmp.spec.js +22 -0
- package/dist/cjs/utils/specialCharacters.js +228 -0
- package/dist/cjs/utils/userAgent.js +28 -0
- package/dist/esm/MarkdownActions.js +186 -0
- package/dist/esm/MarkdownEditor.js +118 -0
- package/dist/esm/__fixtures__/FakeSdk.js +173 -0
- package/dist/esm/__fixtures__/asset/index.js +6 -0
- package/dist/esm/__fixtures__/content-type/index.js +2 -0
- package/dist/esm/__fixtures__/entry/index.js +5 -0
- package/dist/esm/__fixtures__/fixtures.js +6 -0
- package/dist/esm/__fixtures__/locale/index.js +15 -0
- package/dist/esm/__fixtures__/space/index.js +2 -0
- package/dist/esm/codemirrorImports.js +5 -0
- package/dist/esm/components/HeadingSelector.js +17 -0
- package/dist/esm/components/InsertLinkSelector.js +37 -0
- package/dist/esm/components/MarkdownBottomBar.js +46 -0
- package/dist/esm/components/MarkdownConstraints.js +25 -0
- package/dist/esm/components/MarkdownPreview.js +195 -0
- package/dist/esm/components/MarkdownTabs.js +74 -0
- package/dist/esm/components/MarkdownTextarea/CodeMirrorWrapper.js +329 -0
- package/dist/esm/components/MarkdownTextarea/MarkdownCommands.js +218 -0
- package/dist/esm/components/MarkdownTextarea/MarkdownTextarea.js +136 -0
- package/dist/esm/components/MarkdownTextarea/createMarkdownEditor.js +52 -0
- package/dist/esm/components/MarkdownToolbar.js +302 -0
- package/dist/esm/components/icons.js +112 -0
- package/dist/esm/dialogs/CheatsheetModalDialog.js +177 -0
- package/dist/esm/dialogs/ConfirmInsertAssetModalDialog.js +37 -0
- package/dist/esm/dialogs/EmdebExternalContentDialog.js +140 -0
- package/dist/esm/dialogs/InsertLinkModalDialog.js +92 -0
- package/dist/esm/dialogs/InsertTableModalDialog.js +78 -0
- package/dist/esm/dialogs/SpecialCharacterModalDialog.js +84 -0
- package/dist/esm/dialogs/ZenModeModalDialog.js +195 -0
- package/dist/esm/dialogs/openMarkdownDialog.js +72 -0
- package/dist/esm/dialogs/renderMarkdownDialog.js +59 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/utils/insertAssetLinks.js +99 -0
- package/dist/esm/utils/insertAssetLinks.spec.js +18 -0
- package/dist/esm/utils/isValidUrl.js +4 -0
- package/dist/esm/utils/linkOrganizer.js +152 -0
- package/dist/esm/utils/linkOrganizer.spec.js +53 -0
- package/dist/esm/utils/replaceMailtoAmp.js +5 -0
- package/dist/esm/utils/replaceMailtoAmp.spec.js +18 -0
- package/dist/esm/utils/specialCharacters.js +218 -0
- package/dist/esm/utils/userAgent.js +13 -0
- package/dist/{MarkdownActions.d.ts → types/MarkdownActions.d.ts} +38 -38
- package/dist/{MarkdownEditor.d.ts → types/MarkdownEditor.d.ts} +22 -22
- package/dist/types/__fixtures__/FakeSdk.d.ts +8 -0
- package/dist/types/__fixtures__/asset/index.d.ts +6 -0
- package/dist/types/__fixtures__/content-type/index.d.ts +2 -0
- package/dist/types/__fixtures__/entry/index.d.ts +5 -0
- package/dist/types/__fixtures__/fixtures.d.ts +6 -0
- package/dist/types/__fixtures__/locale/index.d.ts +42 -0
- package/dist/types/__fixtures__/space/index.d.ts +2 -0
- package/dist/{codemirrorImports.d.ts → types/codemirrorImports.d.ts} +5 -5
- package/dist/{components → types/components}/HeadingSelector.d.ts +7 -7
- package/dist/{components → types/components}/InsertLinkSelector.d.ts +9 -9
- package/dist/{components → types/components}/MarkdownBottomBar.d.ts +11 -11
- package/dist/{components → types/components}/MarkdownConstraints.d.ts +6 -6
- package/dist/{components → types/components}/MarkdownPreview.d.ts +14 -14
- package/dist/{components → types/components}/MarkdownTabs.d.ts +8 -8
- package/dist/{components → types/components}/MarkdownTextarea/CodeMirrorWrapper.d.ts +58 -58
- package/dist/{components → types/components}/MarkdownTextarea/MarkdownCommands.d.ts +33 -33
- package/dist/{components → types/components}/MarkdownTextarea/MarkdownTextarea.d.ts +17 -17
- package/dist/{components → types/components}/MarkdownTextarea/createMarkdownEditor.d.ts +55 -55
- package/dist/{components → types/components}/MarkdownToolbar.d.ts +12 -12
- package/dist/types/components/icons.d.ts +18 -0
- package/dist/{dialogs → types/dialogs}/CheatsheetModalDialog.d.ts +4 -4
- package/dist/{dialogs → types/dialogs}/ConfirmInsertAssetModalDialog.d.ts +23 -23
- package/dist/{dialogs → types/dialogs}/EmdebExternalContentDialog.d.ts +9 -9
- package/dist/{dialogs → types/dialogs}/InsertLinkModalDialog.d.ts +17 -17
- package/dist/{dialogs → types/dialogs}/InsertTableModalDialog.d.ts +13 -13
- package/dist/{dialogs → types/dialogs}/SpecialCharacterModalDialog.d.ts +9 -9
- package/dist/{dialogs → types/dialogs}/ZenModeModalDialog.d.ts +24 -24
- package/dist/{dialogs → types/dialogs}/openMarkdownDialog.d.ts +5 -5
- package/dist/{dialogs → types/dialogs}/renderMarkdownDialog.d.ts +8 -8
- package/dist/{index.d.ts → types/index.d.ts} +5 -5
- package/dist/{types.d.ts → types/types.d.ts} +75 -75
- package/dist/{utils → types/utils}/insertAssetLinks.d.ts +29 -29
- package/dist/types/utils/insertAssetLinks.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/isValidUrl.d.ts +2 -2
- package/dist/{utils → types/utils}/linkOrganizer.d.ts +6 -6
- package/dist/types/utils/linkOrganizer.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/replaceMailtoAmp.d.ts +1 -1
- package/dist/types/utils/replaceMailtoAmp.spec.d.ts +1 -0
- package/dist/{utils → types/utils}/specialCharacters.d.ts +4 -4
- package/dist/{utils → types/utils}/userAgent.d.ts +1 -1
- package/package.json +25 -11
- package/CHANGELOG.md +0 -314
- package/dist/components/icons.d.ts +0 -18
- package/dist/field-editor-markdown.cjs.development.js +0 -3609
- package/dist/field-editor-markdown.cjs.development.js.map +0 -1
- package/dist/field-editor-markdown.cjs.production.min.js +0 -216
- package/dist/field-editor-markdown.cjs.production.min.js.map +0 -1
- package/dist/field-editor-markdown.esm.js +0 -3599
- package/dist/field-editor-markdown.esm.js.map +0 -1
- package/dist/index.js +0 -8
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { ModalContent, ModalControls, Text, TextLink, Button, Checkbox, Radio, Form, FormControl, TextInput } from '@contentful/f36-components';
|
|
3
|
+
import tokens from '@contentful/f36-tokens';
|
|
4
|
+
import { css } from 'emotion';
|
|
5
|
+
import { MarkdownDialogType } from '../types';
|
|
6
|
+
import { isValidUrl } from '../utils/isValidUrl';
|
|
7
|
+
const styles = {
|
|
8
|
+
widthFiledGroup: css({
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexWrap: 'nowrap',
|
|
11
|
+
alignItems: 'flex-start'
|
|
12
|
+
}),
|
|
13
|
+
radioButtonGroup: css({
|
|
14
|
+
display: 'inline-flex',
|
|
15
|
+
alignItems: 'flex-start',
|
|
16
|
+
paddingTop: tokens.spacingXl
|
|
17
|
+
}),
|
|
18
|
+
radioButton: css({
|
|
19
|
+
marginLeft: tokens.spacingM
|
|
20
|
+
})
|
|
21
|
+
};
|
|
22
|
+
const makeEmbedlyLink = ({ url , width , selectedUnit , attachSocial })=>{
|
|
23
|
+
const s = {
|
|
24
|
+
percent: '%',
|
|
25
|
+
px: 'px'
|
|
26
|
+
};
|
|
27
|
+
return [
|
|
28
|
+
'<a href="' + url + '" class="embedly-card" ',
|
|
29
|
+
'data-card-width="' + width + s[selectedUnit] + '" ',
|
|
30
|
+
'data-card-controls="' + (attachSocial ? '1' : '0') + '"',
|
|
31
|
+
'>Embedded content: ' + url + '</a>'
|
|
32
|
+
].join('');
|
|
33
|
+
};
|
|
34
|
+
const isWidthValid = (width, unit)=>unit === 'percent' ? width <= 100 : true;
|
|
35
|
+
export const EmbedExternalContentModal = ({ onClose })=>{
|
|
36
|
+
const mainInputRef = useRef(null);
|
|
37
|
+
const [url, setUrl] = useState('https://');
|
|
38
|
+
const [selectedUnit, setUnit] = useState('percent');
|
|
39
|
+
const [urlIsValid, setUrlValidity] = useState(true);
|
|
40
|
+
const [width, setWidth] = useState('100');
|
|
41
|
+
const [attachSocial, setAttachSocial] = useState(false);
|
|
42
|
+
useEffect(()=>{
|
|
43
|
+
if (mainInputRef.current?.focus) {
|
|
44
|
+
mainInputRef.current.focus();
|
|
45
|
+
}
|
|
46
|
+
}, [
|
|
47
|
+
mainInputRef
|
|
48
|
+
]);
|
|
49
|
+
return React.createElement(React.Fragment, null, React.createElement(ModalContent, {
|
|
50
|
+
testId: "embed-external-dialog"
|
|
51
|
+
}, React.createElement(Form, null, React.createElement(FormControl, {
|
|
52
|
+
id: "external-link-url-field",
|
|
53
|
+
isRequired: true,
|
|
54
|
+
isInvalid: !urlIsValid
|
|
55
|
+
}, React.createElement(FormControl.Label, null, "Content URL"), React.createElement(TextInput, {
|
|
56
|
+
name: "external-link-url",
|
|
57
|
+
value: url,
|
|
58
|
+
onChange: (e)=>{
|
|
59
|
+
const value = e.target.value;
|
|
60
|
+
setUrl(value);
|
|
61
|
+
setUrlValidity(isValidUrl(value));
|
|
62
|
+
},
|
|
63
|
+
testId: "external-link-url-field",
|
|
64
|
+
placeholder: "https://example.com",
|
|
65
|
+
ref: mainInputRef
|
|
66
|
+
}), React.createElement(FormControl.HelpText, null, "Include protocol (e.g. https://)"), !urlIsValid && React.createElement(FormControl.ValidationMessage, null, "URL is invalid")), React.createElement(TextLink, {
|
|
67
|
+
href: "http://embed.ly/providers",
|
|
68
|
+
target: "_blank",
|
|
69
|
+
rel: "noopener noreferrer"
|
|
70
|
+
}, "Supported sources"), React.createElement("div", {
|
|
71
|
+
className: styles.widthFiledGroup
|
|
72
|
+
}, React.createElement(FormControl, {
|
|
73
|
+
id: "embedded-content-width",
|
|
74
|
+
isRequired: true,
|
|
75
|
+
isInvalid: !isWidthValid(Number(width), selectedUnit)
|
|
76
|
+
}, React.createElement(FormControl.Label, null, "Width"), React.createElement(TextInput, {
|
|
77
|
+
value: width,
|
|
78
|
+
name: "embedded-content-width",
|
|
79
|
+
testId: "embedded-content-width",
|
|
80
|
+
type: "number",
|
|
81
|
+
width: "small",
|
|
82
|
+
onChange: (e)=>setWidth(e.target.value)
|
|
83
|
+
}), !isWidthValid(Number(width), selectedUnit) && React.createElement(FormControl.ValidationMessage, null, "Should be equal or less then 100")), React.createElement("div", {
|
|
84
|
+
className: styles.radioButtonGroup
|
|
85
|
+
}, React.createElement(Radio, {
|
|
86
|
+
id: "unit-option-percent",
|
|
87
|
+
value: "percent",
|
|
88
|
+
isChecked: selectedUnit === 'percent',
|
|
89
|
+
onChange: ()=>setUnit('percent'),
|
|
90
|
+
className: styles.radioButton
|
|
91
|
+
}, "percent"), React.createElement(Radio, {
|
|
92
|
+
id: "unit-option-pixels",
|
|
93
|
+
value: "pixels",
|
|
94
|
+
isChecked: selectedUnit === 'px',
|
|
95
|
+
onChange: ()=>setUnit('px'),
|
|
96
|
+
className: styles.radioButton
|
|
97
|
+
}, "pixels"))), React.createElement(Checkbox, {
|
|
98
|
+
id: "attach-social-checkbox",
|
|
99
|
+
name: "attach-social-checkbox",
|
|
100
|
+
value: "Yes",
|
|
101
|
+
isChecked: attachSocial,
|
|
102
|
+
onChange: ()=>setAttachSocial(!attachSocial),
|
|
103
|
+
testId: "attach-social-checkbox"
|
|
104
|
+
}, "Attach social sharing links to this element"), React.createElement(Text, {
|
|
105
|
+
as: "p",
|
|
106
|
+
fontColor: "gray500",
|
|
107
|
+
marginTop: "spacingXs"
|
|
108
|
+
}, "To enable this embedded content in your application make sure to add the\xa0", React.createElement(TextLink, {
|
|
109
|
+
href: "http://embed.ly/docs/products/cards",
|
|
110
|
+
target: "_blank",
|
|
111
|
+
rel: "noopener noreferrer"
|
|
112
|
+
}, "Embedly's platform.js"), "\xa0on your development environment"))), React.createElement(ModalControls, null, React.createElement(Button, {
|
|
113
|
+
testId: "emded-external-cancel",
|
|
114
|
+
onClick: ()=>onClose(false),
|
|
115
|
+
variant: "secondary",
|
|
116
|
+
size: "small"
|
|
117
|
+
}, "Cancel"), React.createElement(Button, {
|
|
118
|
+
testId: "embed-external-confirm",
|
|
119
|
+
onClick: ()=>onClose(makeEmbedlyLink({
|
|
120
|
+
url,
|
|
121
|
+
width: Number(width),
|
|
122
|
+
selectedUnit,
|
|
123
|
+
attachSocial
|
|
124
|
+
})),
|
|
125
|
+
variant: "positive",
|
|
126
|
+
size: "small"
|
|
127
|
+
}, "Insert")));
|
|
128
|
+
};
|
|
129
|
+
export const openEmbedExternalContentDialog = (dialogs)=>{
|
|
130
|
+
return dialogs.openCurrent({
|
|
131
|
+
title: 'Embed external content',
|
|
132
|
+
width: 'large',
|
|
133
|
+
minHeight: '435px',
|
|
134
|
+
shouldCloseOnEscapePress: true,
|
|
135
|
+
shouldCloseOnOverlayClick: true,
|
|
136
|
+
parameters: {
|
|
137
|
+
type: MarkdownDialogType.embedExternalContent
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { ModalContent, ModalControls, Button, Form, FormControl, TextInput } from '@contentful/f36-components';
|
|
3
|
+
import { MarkdownDialogType } from '../types';
|
|
4
|
+
import { isValidUrl } from '../utils/isValidUrl';
|
|
5
|
+
export const InsertLinkModal = ({ selectedText , onClose })=>{
|
|
6
|
+
const mainInputRef = useRef(null);
|
|
7
|
+
const [text, setText] = useState(selectedText || '');
|
|
8
|
+
const [url, setUrl] = useState('');
|
|
9
|
+
const [touched, setTouched] = useState(false);
|
|
10
|
+
const [title, setTitle] = useState('');
|
|
11
|
+
const onInsert = (values)=>onClose(values);
|
|
12
|
+
const urlIsValid = isValidUrl(url);
|
|
13
|
+
useEffect(()=>{
|
|
14
|
+
if (mainInputRef?.current?.focus) {
|
|
15
|
+
mainInputRef.current.focus();
|
|
16
|
+
}
|
|
17
|
+
}, [
|
|
18
|
+
mainInputRef
|
|
19
|
+
]);
|
|
20
|
+
return React.createElement(React.Fragment, null, React.createElement(ModalContent, {
|
|
21
|
+
testId: "insert-link-modal"
|
|
22
|
+
}, React.createElement(Form, {
|
|
23
|
+
onSubmit: ()=>onInsert({
|
|
24
|
+
url,
|
|
25
|
+
text,
|
|
26
|
+
title
|
|
27
|
+
})
|
|
28
|
+
}, React.createElement(FormControl, {
|
|
29
|
+
id: "link-text-field",
|
|
30
|
+
isDisabled: Boolean(selectedText)
|
|
31
|
+
}, React.createElement(FormControl.Label, null, "Link text"), React.createElement(TextInput, {
|
|
32
|
+
name: "link-text",
|
|
33
|
+
value: text,
|
|
34
|
+
onChange: (e)=>{
|
|
35
|
+
setText(e.target.value);
|
|
36
|
+
},
|
|
37
|
+
testId: "link-text-field"
|
|
38
|
+
})), React.createElement(FormControl, {
|
|
39
|
+
id: "target-url-field",
|
|
40
|
+
isInvalid: touched && !urlIsValid
|
|
41
|
+
}, React.createElement(FormControl.Label, null, "Target URL"), React.createElement(TextInput, {
|
|
42
|
+
name: "target-url",
|
|
43
|
+
value: url,
|
|
44
|
+
onChange: (e)=>{
|
|
45
|
+
setUrl(e.target.value);
|
|
46
|
+
setTouched(true);
|
|
47
|
+
},
|
|
48
|
+
placeholder: "https://",
|
|
49
|
+
maxLength: 2100,
|
|
50
|
+
testId: "target-url-field",
|
|
51
|
+
ref: mainInputRef
|
|
52
|
+
}), React.createElement(FormControl.HelpText, null, "Include protocol (e.g. https://)"), touched && !urlIsValid && React.createElement(FormControl.ValidationMessage, null, "Invalid URL")), React.createElement(FormControl, {
|
|
53
|
+
id: "link-title-field"
|
|
54
|
+
}, React.createElement(FormControl.Label, null, "Link title"), React.createElement(TextInput, {
|
|
55
|
+
name: "link-title",
|
|
56
|
+
value: title,
|
|
57
|
+
onChange: (e)=>{
|
|
58
|
+
setTitle(e.target.value);
|
|
59
|
+
},
|
|
60
|
+
testId: "link-title-field"
|
|
61
|
+
}), React.createElement(FormControl.HelpText, null, "Extra link information, usually shown as a tooltip on mouse hover")))), React.createElement(ModalControls, null, React.createElement(Button, {
|
|
62
|
+
testId: "insert-link-cancel",
|
|
63
|
+
onClick: ()=>onClose(false),
|
|
64
|
+
variant: "secondary",
|
|
65
|
+
size: "small"
|
|
66
|
+
}, "Cancel"), React.createElement(Button, {
|
|
67
|
+
testId: "insert-link-confirm",
|
|
68
|
+
onClick: ()=>{
|
|
69
|
+
onInsert({
|
|
70
|
+
url,
|
|
71
|
+
text,
|
|
72
|
+
title
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
isDisabled: !urlIsValid,
|
|
76
|
+
variant: "positive",
|
|
77
|
+
size: "small"
|
|
78
|
+
}, "Insert")));
|
|
79
|
+
};
|
|
80
|
+
export const openInsertLinkDialog = (dialogs, params)=>{
|
|
81
|
+
return dialogs.openCurrent({
|
|
82
|
+
title: 'Insert link',
|
|
83
|
+
width: 'large',
|
|
84
|
+
minHeight: '410px',
|
|
85
|
+
shouldCloseOnEscapePress: true,
|
|
86
|
+
shouldCloseOnOverlayClick: true,
|
|
87
|
+
parameters: {
|
|
88
|
+
type: MarkdownDialogType.insertLink,
|
|
89
|
+
...params
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { ModalContent, ModalControls, Button, Form, FormControl, TextInput } from '@contentful/f36-components';
|
|
3
|
+
import inRange from 'lodash/inRange';
|
|
4
|
+
import { MarkdownDialogType } from '../types';
|
|
5
|
+
export const InsertTableModal = ({ onClose })=>{
|
|
6
|
+
const mainInputRef = useRef(null);
|
|
7
|
+
const [rows, setRows] = useState(2);
|
|
8
|
+
const [cols, setColumns] = useState(1);
|
|
9
|
+
const rowsAreValid = inRange(rows, 2, 100);
|
|
10
|
+
const colsAreValid = inRange(cols, 1, 100);
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
if (mainInputRef.current?.focus) {
|
|
13
|
+
mainInputRef.current.focus();
|
|
14
|
+
}
|
|
15
|
+
}, [
|
|
16
|
+
mainInputRef
|
|
17
|
+
]);
|
|
18
|
+
return React.createElement(React.Fragment, null, React.createElement(ModalContent, {
|
|
19
|
+
testId: "insert-table-modal"
|
|
20
|
+
}, React.createElement(Form, null, React.createElement(FormControl, {
|
|
21
|
+
id: "insert-table-rows-number-field",
|
|
22
|
+
isRequired: true,
|
|
23
|
+
isInvalid: !rowsAreValid
|
|
24
|
+
}, React.createElement(FormControl.Label, null, "Number of rows"), React.createElement(TextInput, {
|
|
25
|
+
name: "rows",
|
|
26
|
+
value: rows.toString(),
|
|
27
|
+
onChange: (e)=>setRows(Number(e.target.value)),
|
|
28
|
+
testId: "insert-table-rows-number-field",
|
|
29
|
+
min: 2,
|
|
30
|
+
max: 100,
|
|
31
|
+
pattern: "[1-9][0-9]*",
|
|
32
|
+
type: "number",
|
|
33
|
+
width: "small",
|
|
34
|
+
autoComplete: "off",
|
|
35
|
+
ref: mainInputRef
|
|
36
|
+
}), !rowsAreValid && React.createElement(FormControl.ValidationMessage, null, "Should be between 2 and 100")), React.createElement(FormControl, {
|
|
37
|
+
id: "insert-table-columns-number-field",
|
|
38
|
+
isRequired: true,
|
|
39
|
+
isInvalid: !colsAreValid
|
|
40
|
+
}, React.createElement(FormControl.Label, null, "Number of columns"), React.createElement(TextInput, {
|
|
41
|
+
name: "columns",
|
|
42
|
+
value: cols.toString(),
|
|
43
|
+
onChange: (e)=>setColumns(Number(e.target.value)),
|
|
44
|
+
testId: "insert-table-columns-number-field",
|
|
45
|
+
min: 1,
|
|
46
|
+
max: 100,
|
|
47
|
+
pattern: "[1-9][0-9]*",
|
|
48
|
+
type: "number",
|
|
49
|
+
width: "small",
|
|
50
|
+
autoComplete: "off"
|
|
51
|
+
}), !colsAreValid && React.createElement(FormControl.ValidationMessage, null, "Should be between 1 and 100")))), React.createElement(ModalControls, null, React.createElement(Button, {
|
|
52
|
+
testId: "insert-table-cancel",
|
|
53
|
+
onClick: ()=>onClose(false),
|
|
54
|
+
variant: "secondary",
|
|
55
|
+
size: "small"
|
|
56
|
+
}, "Cancel"), React.createElement(Button, {
|
|
57
|
+
testId: "insert-table-confirm",
|
|
58
|
+
onClick: ()=>onClose({
|
|
59
|
+
rows,
|
|
60
|
+
cols
|
|
61
|
+
}),
|
|
62
|
+
variant: "positive",
|
|
63
|
+
size: "small",
|
|
64
|
+
isDisabled: !rowsAreValid || !colsAreValid
|
|
65
|
+
}, "Insert")));
|
|
66
|
+
};
|
|
67
|
+
export const openInsertTableDialog = (dialogs)=>{
|
|
68
|
+
return dialogs.openCurrent({
|
|
69
|
+
title: 'Insert table',
|
|
70
|
+
width: 'medium',
|
|
71
|
+
minHeight: '260px',
|
|
72
|
+
shouldCloseOnEscapePress: true,
|
|
73
|
+
shouldCloseOnOverlayClick: true,
|
|
74
|
+
parameters: {
|
|
75
|
+
type: MarkdownDialogType.insertTable
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ModalContent, ModalControls, Text, Flex, Button, Tooltip } from '@contentful/f36-components';
|
|
3
|
+
import tokens from '@contentful/f36-tokens';
|
|
4
|
+
import { css } from 'emotion';
|
|
5
|
+
import { MarkdownDialogType } from '../types';
|
|
6
|
+
import { specialCharacters } from '../utils/specialCharacters';
|
|
7
|
+
const styles = {
|
|
8
|
+
buttonPanel: css({
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexWrap: 'wrap'
|
|
11
|
+
}),
|
|
12
|
+
charButton: css({
|
|
13
|
+
border: `1px solid ${tokens.gray500}`,
|
|
14
|
+
width: '4.1rem',
|
|
15
|
+
height: '4.1rem',
|
|
16
|
+
fontSize: tokens.fontSizeXl,
|
|
17
|
+
marginTop: tokens.spacing2Xs,
|
|
18
|
+
marginRight: tokens.spacing2Xs
|
|
19
|
+
}),
|
|
20
|
+
selectedCharButton: css({
|
|
21
|
+
backgroundColor: tokens.gray100
|
|
22
|
+
}),
|
|
23
|
+
tooltip: css({
|
|
24
|
+
zIndex: 1000
|
|
25
|
+
}),
|
|
26
|
+
button: css({
|
|
27
|
+
marginTop: tokens.spacingM,
|
|
28
|
+
marginRight: tokens.spacingS
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
export const SpecialCharacterModalDialog = ({ onClose })=>{
|
|
32
|
+
const [selectedCharacter, setSelectedCharacter] = useState(specialCharacters[0]);
|
|
33
|
+
return React.createElement(React.Fragment, null, React.createElement(ModalContent, {
|
|
34
|
+
testId: "insert-special-character-modal"
|
|
35
|
+
}, React.createElement(Flex, {
|
|
36
|
+
flexDirection: "column",
|
|
37
|
+
alignItems: "center"
|
|
38
|
+
}, React.createElement(Text, {
|
|
39
|
+
as: "div",
|
|
40
|
+
lineHeight: "lineHeight3Xl",
|
|
41
|
+
fontSize: "fontSize3Xl",
|
|
42
|
+
marginBottom: "spacingS"
|
|
43
|
+
}, String.fromCharCode(selectedCharacter.code)), React.createElement(Text, {
|
|
44
|
+
as: "div",
|
|
45
|
+
marginBottom: "spacingS"
|
|
46
|
+
}, selectedCharacter.desc)), React.createElement("div", {
|
|
47
|
+
className: styles.buttonPanel
|
|
48
|
+
}, specialCharacters.map((char)=>React.createElement("div", {
|
|
49
|
+
key: char.code
|
|
50
|
+
}, React.createElement(Tooltip, {
|
|
51
|
+
className: styles.tooltip,
|
|
52
|
+
content: char.desc
|
|
53
|
+
}, React.createElement(Button, {
|
|
54
|
+
testId: "special-character-button",
|
|
55
|
+
isActive: char.code === selectedCharacter.code,
|
|
56
|
+
className: styles.charButton,
|
|
57
|
+
variant: "transparent",
|
|
58
|
+
onClick: ()=>setSelectedCharacter(char)
|
|
59
|
+
}, String.fromCharCode(char.code))))))), React.createElement(ModalControls, null, React.createElement(Button, {
|
|
60
|
+
testId: "insert-character-cancel",
|
|
61
|
+
className: styles.button,
|
|
62
|
+
onClick: ()=>onClose(false),
|
|
63
|
+
variant: "secondary",
|
|
64
|
+
size: "small"
|
|
65
|
+
}, "Cancel"), React.createElement(Button, {
|
|
66
|
+
className: styles.button,
|
|
67
|
+
testId: "insert-character-confirm",
|
|
68
|
+
onClick: ()=>onClose(String.fromCharCode(selectedCharacter.code)),
|
|
69
|
+
variant: "positive",
|
|
70
|
+
size: "small"
|
|
71
|
+
}, "Insert selected")));
|
|
72
|
+
};
|
|
73
|
+
export const openInsertSpecialCharacter = (dialogs)=>{
|
|
74
|
+
return dialogs.openCurrent({
|
|
75
|
+
title: 'Insert special character',
|
|
76
|
+
width: 'large',
|
|
77
|
+
minHeight: '600px',
|
|
78
|
+
shouldCloseOnEscapePress: true,
|
|
79
|
+
shouldCloseOnOverlayClick: true,
|
|
80
|
+
parameters: {
|
|
81
|
+
type: MarkdownDialogType.insertSpecialCharacter
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@contentful/f36-icons';
|
|
3
|
+
import tokens from '@contentful/f36-tokens';
|
|
4
|
+
import { css, cx } from 'emotion';
|
|
5
|
+
import { MarkdownBottomBar, MarkdownHelp } from '../components/MarkdownBottomBar';
|
|
6
|
+
import { MarkdownPreview } from '../components/MarkdownPreview';
|
|
7
|
+
import { MarkdownTextarea } from '../components/MarkdownTextarea/MarkdownTextarea';
|
|
8
|
+
import { MarkdownToolbar } from '../components/MarkdownToolbar';
|
|
9
|
+
import { openCheatsheetModal } from '../dialogs/CheatsheetModalDialog';
|
|
10
|
+
import { createMarkdownActions } from '../MarkdownActions';
|
|
11
|
+
import { MarkdownDialogType } from '../types';
|
|
12
|
+
const styles = {
|
|
13
|
+
root: css({
|
|
14
|
+
position: 'fixed',
|
|
15
|
+
left: 0,
|
|
16
|
+
right: 0,
|
|
17
|
+
top: 0,
|
|
18
|
+
bottom: 0
|
|
19
|
+
}),
|
|
20
|
+
topSplit: css({
|
|
21
|
+
position: 'fixed',
|
|
22
|
+
top: 0,
|
|
23
|
+
height: '48px',
|
|
24
|
+
left: 0,
|
|
25
|
+
right: 0
|
|
26
|
+
}),
|
|
27
|
+
bottomSplit: css({
|
|
28
|
+
position: 'fixed',
|
|
29
|
+
bottom: 0,
|
|
30
|
+
left: 0,
|
|
31
|
+
right: 0,
|
|
32
|
+
height: '36px'
|
|
33
|
+
}),
|
|
34
|
+
editorSplit: css({
|
|
35
|
+
width: '50%',
|
|
36
|
+
position: 'fixed',
|
|
37
|
+
top: '48px',
|
|
38
|
+
left: 0,
|
|
39
|
+
bottom: '36px',
|
|
40
|
+
overflowX: 'hidden',
|
|
41
|
+
overflowY: 'scroll'
|
|
42
|
+
}),
|
|
43
|
+
editorSplitFullscreen: css({
|
|
44
|
+
left: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
width: '100%'
|
|
47
|
+
}),
|
|
48
|
+
previewSplit: css({
|
|
49
|
+
width: '50%',
|
|
50
|
+
position: 'fixed',
|
|
51
|
+
top: '48px',
|
|
52
|
+
right: 0,
|
|
53
|
+
bottom: '36px',
|
|
54
|
+
overflowX: 'hidden',
|
|
55
|
+
overflowY: 'scroll'
|
|
56
|
+
}),
|
|
57
|
+
separator: css({
|
|
58
|
+
position: 'fixed',
|
|
59
|
+
top: '48px',
|
|
60
|
+
bottom: '36px',
|
|
61
|
+
width: '1px',
|
|
62
|
+
background: tokens.gray400,
|
|
63
|
+
left: '50%'
|
|
64
|
+
}),
|
|
65
|
+
button: css({
|
|
66
|
+
position: 'fixed',
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
zIndex: 105,
|
|
69
|
+
top: '49%',
|
|
70
|
+
height: '30px',
|
|
71
|
+
backgroundColor: tokens.gray100,
|
|
72
|
+
border: `1px solid ${tokens.gray400}`,
|
|
73
|
+
padding: 0
|
|
74
|
+
}),
|
|
75
|
+
hideButton: css({
|
|
76
|
+
left: '50%'
|
|
77
|
+
}),
|
|
78
|
+
showButton: css({
|
|
79
|
+
right: 0,
|
|
80
|
+
borderRightWidth: 0
|
|
81
|
+
}),
|
|
82
|
+
icon: css({
|
|
83
|
+
verticalAlign: 'middle'
|
|
84
|
+
})
|
|
85
|
+
};
|
|
86
|
+
export const ZenModeModalDialog = (props)=>{
|
|
87
|
+
const [currentValue, setCurrentValue] = React.useState(props.initialValue ?? '');
|
|
88
|
+
const [showPreview, setShowPreview] = React.useState(true);
|
|
89
|
+
const [editor, setEditor] = React.useState(null);
|
|
90
|
+
React.useEffect(()=>{
|
|
91
|
+
props.sdk?.window?.updateHeight('100%');
|
|
92
|
+
}, []);
|
|
93
|
+
React.useEffect(()=>{
|
|
94
|
+
setTimeout(()=>{
|
|
95
|
+
editor?.setFullsize();
|
|
96
|
+
editor?.refresh();
|
|
97
|
+
}, 150);
|
|
98
|
+
}, [
|
|
99
|
+
editor
|
|
100
|
+
]);
|
|
101
|
+
const actions = React.useMemo(()=>{
|
|
102
|
+
return createMarkdownActions({
|
|
103
|
+
sdk: props.sdk,
|
|
104
|
+
editor,
|
|
105
|
+
locale: props.locale
|
|
106
|
+
});
|
|
107
|
+
}, [
|
|
108
|
+
editor
|
|
109
|
+
]);
|
|
110
|
+
actions.closeZenMode = ()=>{
|
|
111
|
+
props.onClose({
|
|
112
|
+
value: currentValue,
|
|
113
|
+
cursor: editor?.getCursor()
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const direction = props.sdk.locales.direction[props.locale] ?? 'ltr';
|
|
117
|
+
return React.createElement("div", {
|
|
118
|
+
className: styles.root,
|
|
119
|
+
"data-test-id": "zen-mode-markdown-editor"
|
|
120
|
+
}, React.createElement("div", {
|
|
121
|
+
className: styles.topSplit
|
|
122
|
+
}, React.createElement(MarkdownToolbar, {
|
|
123
|
+
mode: "zen",
|
|
124
|
+
disabled: false,
|
|
125
|
+
canUploadAssets: false,
|
|
126
|
+
actions: actions
|
|
127
|
+
})), React.createElement("div", {
|
|
128
|
+
className: cx(styles.editorSplit, {
|
|
129
|
+
[styles.editorSplitFullscreen]: showPreview === false
|
|
130
|
+
})
|
|
131
|
+
}, React.createElement(MarkdownTextarea, {
|
|
132
|
+
mode: "zen",
|
|
133
|
+
visible: true,
|
|
134
|
+
disabled: false,
|
|
135
|
+
direction: direction,
|
|
136
|
+
onReady: (editor)=>{
|
|
137
|
+
editor.setContent(props.initialValue ?? '');
|
|
138
|
+
editor.setReadOnly(false);
|
|
139
|
+
setEditor(editor);
|
|
140
|
+
editor.focus();
|
|
141
|
+
editor.events.onChange((value)=>{
|
|
142
|
+
setCurrentValue(value);
|
|
143
|
+
props.saveValueToSDK(value);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
})), showPreview && React.createElement("div", {
|
|
147
|
+
className: styles.previewSplit
|
|
148
|
+
}, React.createElement(MarkdownPreview, {
|
|
149
|
+
direction: direction,
|
|
150
|
+
mode: "zen",
|
|
151
|
+
value: currentValue,
|
|
152
|
+
previewComponents: props.previewComponents
|
|
153
|
+
})), showPreview && React.createElement("div", {
|
|
154
|
+
className: styles.separator
|
|
155
|
+
}), showPreview && React.createElement("button", {
|
|
156
|
+
className: cx(styles.button, styles.hideButton),
|
|
157
|
+
"aria-label": "Hide preview",
|
|
158
|
+
onClick: ()=>{
|
|
159
|
+
setShowPreview(false);
|
|
160
|
+
}
|
|
161
|
+
}, React.createElement(ChevronRightIcon, {
|
|
162
|
+
variant: "muted",
|
|
163
|
+
size: "tiny",
|
|
164
|
+
className: styles.icon
|
|
165
|
+
})), !showPreview && React.createElement("button", {
|
|
166
|
+
className: cx(styles.button, styles.showButton),
|
|
167
|
+
"aria-label": "Show preview",
|
|
168
|
+
onClick: ()=>{
|
|
169
|
+
setShowPreview(true);
|
|
170
|
+
}
|
|
171
|
+
}, React.createElement(ChevronLeftIcon, {
|
|
172
|
+
variant: "muted",
|
|
173
|
+
size: "tiny",
|
|
174
|
+
className: styles.icon
|
|
175
|
+
})), React.createElement("div", {
|
|
176
|
+
className: styles.bottomSplit
|
|
177
|
+
}, React.createElement(MarkdownBottomBar, null, React.createElement(MarkdownHelp, {
|
|
178
|
+
onClick: ()=>{
|
|
179
|
+
openCheatsheetModal(props.sdk.dialogs);
|
|
180
|
+
}
|
|
181
|
+
}))));
|
|
182
|
+
};
|
|
183
|
+
export const openZenMode = (dialogs, options)=>{
|
|
184
|
+
return dialogs.openCurrent({
|
|
185
|
+
width: 'zen',
|
|
186
|
+
shouldCloseOnEscapePress: false,
|
|
187
|
+
minHeight: '100vh',
|
|
188
|
+
shouldCloseOnOverlayClick: false,
|
|
189
|
+
parameters: {
|
|
190
|
+
type: MarkdownDialogType.zenMode,
|
|
191
|
+
initialValue: options.initialValue,
|
|
192
|
+
locale: options.locale
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ModalDialogLauncher } from '@contentful/field-editor-shared';
|
|
3
|
+
import { MarkdownDialogType } from '../types';
|
|
4
|
+
import { CheatsheetModalDialog } from './CheatsheetModalDialog';
|
|
5
|
+
import { ConfirmInsertAssetModalDialog } from './ConfirmInsertAssetModalDialog';
|
|
6
|
+
import { EmbedExternalContentModal } from './EmdebExternalContentDialog';
|
|
7
|
+
import { InsertLinkModal } from './InsertLinkModalDialog';
|
|
8
|
+
import { InsertTableModal } from './InsertTableModalDialog';
|
|
9
|
+
import { SpecialCharacterModalDialog } from './SpecialCharacterModalDialog';
|
|
10
|
+
import { ZenModeModalDialog } from './ZenModeModalDialog';
|
|
11
|
+
export const openMarkdownDialog = (sdk, previewComponents)=>(options)=>{
|
|
12
|
+
if (options.parameters?.type === MarkdownDialogType.cheatsheet) {
|
|
13
|
+
return ModalDialogLauncher.openDialog(options, ()=>{
|
|
14
|
+
return React.createElement(CheatsheetModalDialog, null);
|
|
15
|
+
});
|
|
16
|
+
} else if (options.parameters?.type === MarkdownDialogType.insertLink) {
|
|
17
|
+
const selectedText = options.parameters.selectedText;
|
|
18
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
19
|
+
return React.createElement(InsertLinkModal, {
|
|
20
|
+
selectedText: selectedText,
|
|
21
|
+
onClose: onClose
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
} else if (options.parameters?.type === MarkdownDialogType.insertSpecialCharacter) {
|
|
25
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
26
|
+
return React.createElement(SpecialCharacterModalDialog, {
|
|
27
|
+
onClose: onClose
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
} else if (options.parameters?.type === MarkdownDialogType.insertTable) {
|
|
31
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
32
|
+
return React.createElement(InsertTableModal, {
|
|
33
|
+
onClose: onClose
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
} else if (options.parameters?.type === MarkdownDialogType.embedExternalContent) {
|
|
37
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
38
|
+
return React.createElement(EmbedExternalContentModal, {
|
|
39
|
+
onClose: onClose
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
} else if (options.parameters?.type === MarkdownDialogType.confirmInsertAsset) {
|
|
43
|
+
const locale = options.parameters.locale;
|
|
44
|
+
const assets = options.parameters.assets;
|
|
45
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
46
|
+
return React.createElement(ConfirmInsertAssetModalDialog, {
|
|
47
|
+
onClose: onClose,
|
|
48
|
+
locale: locale,
|
|
49
|
+
assets: assets
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
} else if (options.parameters?.type === MarkdownDialogType.zenMode) {
|
|
53
|
+
const initialValue = options.parameters.initialValue;
|
|
54
|
+
const locale = options.parameters.locale;
|
|
55
|
+
return ModalDialogLauncher.openDialog(options, ({ onClose })=>{
|
|
56
|
+
return React.createElement(ZenModeModalDialog, {
|
|
57
|
+
saveValueToSDK: (value)=>{
|
|
58
|
+
if (value) {
|
|
59
|
+
return sdk?.field?.setValue(value);
|
|
60
|
+
}
|
|
61
|
+
return sdk?.field?.removeValue();
|
|
62
|
+
},
|
|
63
|
+
onClose: onClose,
|
|
64
|
+
initialValue: initialValue,
|
|
65
|
+
locale: locale,
|
|
66
|
+
sdk: sdk,
|
|
67
|
+
previewComponents: previewComponents
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return Promise.reject();
|
|
72
|
+
};
|