@churchapps/apphelper 0.0.7 → 0.0.9
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/.eslintignore +3 -3
- package/.eslintrc.json +22 -22
- package/LICENSE +21 -21
- package/README.md +16 -16
- package/dist/components/ExportLink.d.ts.map +1 -1
- package/dist/components/ExportLink.js +9 -7
- package/dist/components/ExportLink.js.map +1 -1
- package/dist/components/FormSubmissionEdit.d.ts +0 -1
- package/dist/components/FormSubmissionEdit.d.ts.map +1 -1
- package/dist/components/FormSubmissionEdit.js +1 -2
- package/dist/components/FormSubmissionEdit.js.map +1 -1
- package/dist/components/markdownEditor/editor.css +787 -787
- package/dist/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
- package/dist/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
- package/dist/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
- package/dist/components/markdownEditor/images/icons/chevron-down.svg +2 -2
- package/dist/components/markdownEditor/images/icons/code.svg +2 -2
- package/dist/components/markdownEditor/images/icons/journal-code.svg +4 -4
- package/dist/components/markdownEditor/images/icons/journal-text.svg +4 -4
- package/dist/components/markdownEditor/images/icons/justify.svg +2 -2
- package/dist/components/markdownEditor/images/icons/link.svg +3 -3
- package/dist/components/markdownEditor/images/icons/list-ol.svg +3 -3
- package/dist/components/markdownEditor/images/icons/list-ul.svg +2 -2
- package/dist/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-center.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-left.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-right.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-bold.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h1.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h2.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h3.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h4.svg +12 -12
- package/dist/components/markdownEditor/images/icons/type-italic.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-underline.svg +2 -2
- package/dist/donationComponents/components/NonAuthDonationInner.d.ts.map +1 -1
- package/dist/donationComponents/components/NonAuthDonationInner.js +38 -5
- package/dist/donationComponents/components/NonAuthDonationInner.js.map +1 -1
- package/package.json +84 -84
- package/src/components/CreatePerson.tsx +80 -80
- package/src/components/DisplayBox.tsx +68 -68
- package/src/components/ErrorMessages.tsx +26 -26
- package/src/components/ExportLink.tsx +66 -61
- package/src/components/FloatingSupport.tsx +16 -16
- package/src/components/FormSubmissionEdit.tsx +120 -122
- package/src/components/HelpIcon.tsx +10 -10
- package/src/components/ImageEditor.tsx +126 -126
- package/src/components/InputBox.tsx +73 -73
- package/src/components/Loading.tsx +29 -29
- package/src/components/PersonAdd.tsx +75 -75
- package/src/components/QuestionEdit.tsx +62 -62
- package/src/components/SmallButton.tsx +39 -39
- package/src/components/SupportModal.tsx +26 -26
- package/src/components/TabPanel.tsx +34 -34
- package/src/components/gallery/GalleryModal.tsx +102 -102
- package/src/components/gallery/StockPhotos.tsx +74 -74
- package/src/components/gallery/index.ts +1 -1
- package/src/components/index.tsx +23 -23
- package/src/components/markdownEditor/Editor.tsx +132 -132
- package/src/components/markdownEditor/MarkdownEditor.tsx +16 -16
- package/src/components/markdownEditor/MarkdownModal.tsx +46 -46
- package/src/components/markdownEditor/MarkdownPreview.tsx +14 -14
- package/src/components/markdownEditor/editor.css +787 -787
- package/src/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
- package/src/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
- package/src/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
- package/src/components/markdownEditor/images/icons/chevron-down.svg +2 -2
- package/src/components/markdownEditor/images/icons/code.svg +2 -2
- package/src/components/markdownEditor/images/icons/journal-code.svg +4 -4
- package/src/components/markdownEditor/images/icons/journal-text.svg +4 -4
- package/src/components/markdownEditor/images/icons/justify.svg +2 -2
- package/src/components/markdownEditor/images/icons/link.svg +3 -3
- package/src/components/markdownEditor/images/icons/list-ol.svg +3 -3
- package/src/components/markdownEditor/images/icons/list-ul.svg +2 -2
- package/src/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-center.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-left.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-right.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-bold.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h1.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h2.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h3.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h4.svg +12 -12
- package/src/components/markdownEditor/images/icons/type-italic.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-underline.svg +2 -2
- package/src/components/markdownEditor/index.ts +2 -2
- package/src/components/markdownEditor/plugins/AutoLinkPlugin.tsx +35 -35
- package/src/components/markdownEditor/plugins/ControlledEditorPlugin.tsx +24 -24
- package/src/components/markdownEditor/plugins/ListMaxIndentLevelPlugin.tsx +68 -68
- package/src/components/markdownEditor/plugins/MarkdownTransformers.ts +106 -106
- package/src/components/markdownEditor/plugins/ReadOnlyPlugin.tsx +15 -15
- package/src/components/markdownEditor/plugins/ToolbarPlugin.tsx +401 -401
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNode.tsx +224 -224
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNodePlugin.tsx +32 -32
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNodeTransformer.tsx +102 -102
- package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.tsx +243 -243
- package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.types.ts +11 -11
- package/src/components/markdownEditor/plugins/emoji/EmojiNode.tsx +95 -95
- package/src/components/markdownEditor/plugins/emoji/EmojiNodeTransform.ts +41 -41
- package/src/components/markdownEditor/plugins/emoji/EmojiPickerPlugin.tsx +152 -152
- package/src/components/markdownEditor/plugins/emoji/EmojisPlugin.tsx +65 -65
- package/src/components/markdownEditor/plugins/index.ts +6 -6
- package/src/components/markdownEditor/theme.ts +65 -65
- package/src/components/material/AppList.tsx +20 -20
- package/src/components/material/ChurchList.tsx +22 -22
- package/src/components/material/NavItem.tsx +41 -41
- package/src/components/material/NewPrivateMessage.tsx +103 -103
- package/src/components/material/PrivateMessageDetails.tsx +23 -23
- package/src/components/material/PrivateMessages.tsx +87 -87
- package/src/components/material/SiteWrapper.tsx +140 -140
- package/src/components/material/UserMenu.tsx +141 -141
- package/src/components/material/iconPicker/IconNamesList.ts +2240 -2240
- package/src/components/material/iconPicker/IconPicker.tsx +153 -153
- package/src/components/material/index.tsx +6 -6
- package/src/components/notes/AddNote.tsx +90 -90
- package/src/components/notes/Conversation.tsx +82 -82
- package/src/components/notes/Conversations.tsx +58 -58
- package/src/components/notes/NewConversation.tsx +78 -78
- package/src/components/notes/Note.tsx +44 -44
- package/src/components/notes/Notes.tsx +52 -52
- package/src/components/notes/index.ts +5 -5
- package/src/components/reporting/ChartReport.tsx +98 -98
- package/src/components/reporting/ReportFilter.tsx +54 -54
- package/src/components/reporting/ReportFilterField.tsx +160 -160
- package/src/components/reporting/ReportOutput.tsx +79 -79
- package/src/components/reporting/ReportWithFilter.tsx +70 -70
- package/src/components/reporting/TableReport.tsx +57 -57
- package/src/components/reporting/TreeReport.tsx +111 -111
- package/src/components/reporting/index.ts +4 -4
- package/src/donationComponents/DonationPage.tsx +136 -136
- package/src/donationComponents/components/BankForm.tsx +159 -159
- package/src/donationComponents/components/CardForm.tsx +104 -104
- package/src/donationComponents/components/DonationForm.tsx +235 -235
- package/src/donationComponents/components/FundDonation.tsx +49 -49
- package/src/donationComponents/components/FundDonations.tsx +39 -39
- package/src/donationComponents/components/NonAuthDonation.tsx +31 -31
- package/src/donationComponents/components/NonAuthDonationInner.tsx +259 -221
- package/src/donationComponents/components/PaymentMethods.tsx +135 -135
- package/src/donationComponents/components/RecurringDonations.tsx +121 -121
- package/src/donationComponents/components/RecurringDonationsEdit.tsx +93 -93
- package/src/donationComponents/components/index.tsx +9 -9
- package/src/donationComponents/index.ts +3 -3
- package/src/donationComponents/modals/DonationPreviewModal.tsx +66 -66
- package/src/helpers/AnalyticsHelper.ts +33 -33
- package/src/helpers/ApiHelper.ts +125 -125
- package/src/helpers/AppearanceHelper.ts +69 -69
- package/src/helpers/ArrayHelper.ts +81 -81
- package/src/helpers/CommonEnvironmentHelper.ts +80 -80
- package/src/helpers/CurrencyHelper.ts +10 -10
- package/src/helpers/DateHelper.ts +108 -108
- package/src/helpers/DonationHelper.ts +26 -26
- package/src/helpers/ErrorHelper.ts +36 -36
- package/src/helpers/EventHelper.ts +52 -52
- package/src/helpers/FileHelper.ts +31 -31
- package/src/helpers/PersonHelper.ts +60 -60
- package/src/helpers/SocketHelper.ts +76 -76
- package/src/helpers/Themes.ts +14 -14
- package/src/helpers/UniqueIdHelper.ts +36 -36
- package/src/helpers/UserHelper.ts +59 -59
- package/src/helpers/createEmotionCache.ts +17 -17
- package/src/helpers/index.ts +18 -18
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useMountedState.ts +16 -16
- package/src/index.ts +6 -6
- package/src/interfaces/Access.ts +24 -24
- package/src/interfaces/Attendance.ts +8 -8
- package/src/interfaces/Content.ts +10 -10
- package/src/interfaces/Doing.ts +24 -24
- package/src/interfaces/Donation.ts +45 -45
- package/src/interfaces/Error.ts +17 -17
- package/src/interfaces/Membership.ts +51 -51
- package/src/interfaces/Messaging.ts +20 -20
- package/src/interfaces/Permissions.ts +68 -68
- package/src/interfaces/Reporting.ts +7 -7
- package/src/interfaces/UserContextInterface.ts +13 -13
- package/src/interfaces/index.ts +13 -13
- package/src/pageComponents/LoginPage.tsx +244 -244
- package/src/pageComponents/LogoutPage.tsx +28 -28
- package/src/pageComponents/components/Forgot.tsx +79 -79
- package/src/pageComponents/components/Login.tsx +54 -54
- package/src/pageComponents/components/LoginSetPassword.tsx +63 -63
- package/src/pageComponents/components/Register.tsx +107 -107
- package/src/pageComponents/components/SelectChurchModal.tsx +41 -41
- package/src/pageComponents/components/SelectChurchRegister.tsx +88 -88
- package/src/pageComponents/components/SelectChurchSearch.tsx +69 -69
- package/src/pageComponents/components/SelectableChurch.tsx +38 -38
- package/src/pageComponents/index.ts +3 -3
- package/tsconfig.json +34 -34
- package/tslint.json +14 -14
|
@@ -1,401 +1,401 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { SELECTION_CHANGE_COMMAND, FORMAT_TEXT_COMMAND, $getSelection, $isRangeSelection, $createParagraphNode, $getNodeByKey } from "lexical";
|
|
5
|
-
import { $wrapNodes, $isAtNodeEnd } from "@lexical/selection";
|
|
6
|
-
import { $getNearestNodeOfType, mergeRegister } from "@lexical/utils";
|
|
7
|
-
import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, $isListNode, ListNode } from "@lexical/list";
|
|
8
|
-
import { createPortal } from "react-dom";
|
|
9
|
-
import { $createHeadingNode, $createQuoteNode, $isHeadingNode } from "@lexical/rich-text";
|
|
10
|
-
import { $isCodeNode, getDefaultCodeLanguage, getCodeLanguages } from "@lexical/code";
|
|
11
|
-
import { Icon } from "@mui/material";
|
|
12
|
-
import FloatingLinkEditor from "./customLink/FloatingLinkEditor";
|
|
13
|
-
import { TOGGLE_CUSTOM_LINK_NODE_COMMAND, $isCustomLinkNode } from "./customLink/CustomLinkNode";
|
|
14
|
-
|
|
15
|
-
const LowPriority = 1;
|
|
16
|
-
|
|
17
|
-
const supportedBlockTypes = new Set(["paragraph", "quote", "code", "h1", "h2", "h3", "h4", "ul", "ol"]);
|
|
18
|
-
|
|
19
|
-
const blockTypeToBlockName: any = {
|
|
20
|
-
code: "Code Block",
|
|
21
|
-
h1: "Large Heading",
|
|
22
|
-
h2: "Small Heading",
|
|
23
|
-
h3: "Heading",
|
|
24
|
-
h4: "Heading",
|
|
25
|
-
h5: "Heading",
|
|
26
|
-
ol: "Numbered List",
|
|
27
|
-
paragraph: "Normal",
|
|
28
|
-
quote: "Quote",
|
|
29
|
-
ul: "Bulleted List"
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function Divider() {
|
|
33
|
-
return <div className="divider" />;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/*
|
|
37
|
-
function positionEditorElement(editor: any, rect: any) {
|
|
38
|
-
if (rect === null) {
|
|
39
|
-
editor.style.opacity = "0";
|
|
40
|
-
editor.style.top = "-1000px";
|
|
41
|
-
editor.style.left = "-1000px";
|
|
42
|
-
} else {
|
|
43
|
-
editor.style.opacity = "1";
|
|
44
|
-
editor.style.top = `${rect.top + rect.height + window.pageYOffset + 10}px`;
|
|
45
|
-
editor.style.left = `${rect.left + window.pageXOffset - editor.offsetWidth / 2 + rect.width / 2}px`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
function Select({ onChange, className, options, value }: { onChange: any, className: any, options: any, value: any }) {
|
|
51
|
-
return (
|
|
52
|
-
<select className={className} onChange={onChange} value={value}>
|
|
53
|
-
<option hidden={true} value="" />
|
|
54
|
-
{options.map((option: any) => (
|
|
55
|
-
<option key={option} value={option}>
|
|
56
|
-
{option}
|
|
57
|
-
</option>
|
|
58
|
-
))}
|
|
59
|
-
</select>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function getSelectedNode(selection: any) {
|
|
64
|
-
const anchor = selection.anchor;
|
|
65
|
-
const focus = selection.focus;
|
|
66
|
-
const anchorNode = selection.anchor.getNode();
|
|
67
|
-
const focusNode = selection.focus.getNode();
|
|
68
|
-
if (anchorNode === focusNode) {
|
|
69
|
-
return anchorNode;
|
|
70
|
-
}
|
|
71
|
-
const isBackward = selection.isBackward();
|
|
72
|
-
if (isBackward) {
|
|
73
|
-
return $isAtNodeEnd(focus) ? anchorNode : focusNode;
|
|
74
|
-
} else {
|
|
75
|
-
return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function BlockOptionsDropdownList({ editor, blockType, toolbarRef, setShowBlockOptionsDropDown }: { editor: any, blockType: any, toolbarRef: any, setShowBlockOptionsDropDown: any }) {
|
|
80
|
-
const dropDownRef = useRef(null);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
const toolbar = toolbarRef.current;
|
|
84
|
-
const dropDown = dropDownRef.current;
|
|
85
|
-
|
|
86
|
-
if (toolbar !== null && dropDown !== null) {
|
|
87
|
-
const { top, left } = toolbar.getBoundingClientRect();
|
|
88
|
-
dropDown.style.top = `${top + 40}px`;
|
|
89
|
-
dropDown.style.left = `${left}px`;
|
|
90
|
-
}
|
|
91
|
-
}, [dropDownRef, toolbarRef]);
|
|
92
|
-
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
const dropDown = dropDownRef.current;
|
|
95
|
-
const toolbar = toolbarRef.current;
|
|
96
|
-
|
|
97
|
-
if (dropDown !== null && toolbar !== null) {
|
|
98
|
-
const handle = (event: any) => {
|
|
99
|
-
const target = event.target;
|
|
100
|
-
|
|
101
|
-
if (!dropDown.contains(target) && !toolbar.contains(target)) {
|
|
102
|
-
setShowBlockOptionsDropDown(false);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
document.addEventListener("click", handle);
|
|
106
|
-
|
|
107
|
-
return () => {
|
|
108
|
-
document.removeEventListener("click", handle);
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}, [dropDownRef, setShowBlockOptionsDropDown, toolbarRef]);
|
|
112
|
-
|
|
113
|
-
const formatParagraph = () => {
|
|
114
|
-
if (blockType !== "paragraph") {
|
|
115
|
-
editor.update(() => {
|
|
116
|
-
const selection = $getSelection();
|
|
117
|
-
|
|
118
|
-
if ($isRangeSelection(selection)) {
|
|
119
|
-
$wrapNodes(selection, () => $createParagraphNode());
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
setShowBlockOptionsDropDown(false);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const formatLargeHeading = () => {
|
|
127
|
-
if (blockType !== "h1") {
|
|
128
|
-
editor.update(() => {
|
|
129
|
-
const selection = $getSelection();
|
|
130
|
-
|
|
131
|
-
if ($isRangeSelection(selection)) {
|
|
132
|
-
$wrapNodes(selection, () => $createHeadingNode("h1"));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
setShowBlockOptionsDropDown(false);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const formatSmallHeading = () => {
|
|
140
|
-
if (blockType !== "h2") {
|
|
141
|
-
editor.update(() => {
|
|
142
|
-
const selection = $getSelection();
|
|
143
|
-
|
|
144
|
-
if ($isRangeSelection(selection)) {
|
|
145
|
-
$wrapNodes(selection, () => $createHeadingNode("h2"));
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
setShowBlockOptionsDropDown(false);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const formatHeading3 = () => {
|
|
153
|
-
if (blockType !== "h3") {
|
|
154
|
-
editor.update(() => {
|
|
155
|
-
const selection = $getSelection();
|
|
156
|
-
if ($isRangeSelection(selection)) { $wrapNodes(selection, () => $createHeadingNode("h3")); }
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
setShowBlockOptionsDropDown(false);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const formatHeading4 = () => {
|
|
163
|
-
if (blockType !== "h4") {
|
|
164
|
-
editor.update(() => {
|
|
165
|
-
const selection = $getSelection();
|
|
166
|
-
if ($isRangeSelection(selection)) { $wrapNodes(selection, () => $createHeadingNode("h4")); }
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
setShowBlockOptionsDropDown(false);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const formatBulletList = () => {
|
|
173
|
-
if (blockType !== "ul") {
|
|
174
|
-
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND);
|
|
175
|
-
} else {
|
|
176
|
-
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
|
177
|
-
}
|
|
178
|
-
setShowBlockOptionsDropDown(false);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const formatNumberedList = () => {
|
|
182
|
-
if (blockType !== "ol") {
|
|
183
|
-
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND);
|
|
184
|
-
} else {
|
|
185
|
-
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
|
186
|
-
}
|
|
187
|
-
setShowBlockOptionsDropDown(false);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const formatQuote = () => {
|
|
191
|
-
if (blockType !== "quote") {
|
|
192
|
-
editor.update(() => {
|
|
193
|
-
const selection = $getSelection();
|
|
194
|
-
|
|
195
|
-
if ($isRangeSelection(selection)) {
|
|
196
|
-
$wrapNodes(selection, () => $createQuoteNode());
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
setShowBlockOptionsDropDown(false);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<div className="dropdown" ref={dropDownRef}>
|
|
205
|
-
<button className="item" onClick={formatParagraph}>
|
|
206
|
-
<span className="icon paragraph" />
|
|
207
|
-
<span className="text">Normal</span>
|
|
208
|
-
{blockType === "paragraph" && <span className="active" />}
|
|
209
|
-
</button>
|
|
210
|
-
<button className="item" onClick={formatLargeHeading}>
|
|
211
|
-
<span className="icon large-heading" />
|
|
212
|
-
<span className="text">Large Heading</span>
|
|
213
|
-
{blockType === "h1" && <span className="active" />}
|
|
214
|
-
</button>
|
|
215
|
-
<button className="item" onClick={formatSmallHeading}>
|
|
216
|
-
<span className="icon small-heading" />
|
|
217
|
-
<span className="text">Small Heading</span>
|
|
218
|
-
{blockType === "h2" && <span className="active" />}
|
|
219
|
-
</button>
|
|
220
|
-
<button className="item" onClick={formatHeading3}>
|
|
221
|
-
<span className="icon h3" />
|
|
222
|
-
<span className="text">Heading 3</span>
|
|
223
|
-
{blockType === "h4" && <span className="active" />}
|
|
224
|
-
</button>
|
|
225
|
-
<button className="item" onClick={formatHeading4}>
|
|
226
|
-
<span className="icon h4" />
|
|
227
|
-
<span className="text">Heading 4</span>
|
|
228
|
-
{blockType === "h4" && <span className="active" />}
|
|
229
|
-
</button>
|
|
230
|
-
<button className="item" onClick={formatBulletList}>
|
|
231
|
-
<span className="icon bullet-list" />
|
|
232
|
-
<span className="text">Bullet List</span>
|
|
233
|
-
{blockType === "ul" && <span className="active" />}
|
|
234
|
-
</button>
|
|
235
|
-
<button className="item" onClick={formatNumberedList}>
|
|
236
|
-
<span className="icon numbered-list" />
|
|
237
|
-
<span className="text">Numbered List</span>
|
|
238
|
-
{blockType === "ol" && <span className="active" />}
|
|
239
|
-
</button>
|
|
240
|
-
<button className="item" onClick={formatQuote}>
|
|
241
|
-
<span className="icon quote" />
|
|
242
|
-
<span className="text">Quote</span>
|
|
243
|
-
{blockType === "quote" && <span className="active" />}
|
|
244
|
-
</button>
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
interface Props {
|
|
250
|
-
goFullScreen: () => void
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function ToolbarPlugin(props: Props) {
|
|
254
|
-
const [editor] = useLexicalComposerContext();
|
|
255
|
-
const toolbarRef = useRef(null);
|
|
256
|
-
const [blockType, setBlockType] = useState("paragraph");
|
|
257
|
-
const [selectedElementKey, setSelectedElementKey] = useState(null);
|
|
258
|
-
const [showBlockOptionsDropDown, setShowBlockOptionsDropDown] = useState(false);
|
|
259
|
-
const [codeLanguage, setCodeLanguage] = useState("");
|
|
260
|
-
const [isLink, setIsLink] = useState(false);
|
|
261
|
-
const [isBold, setIsBold] = useState(false);
|
|
262
|
-
const [isItalic, setIsItalic] = useState(false);
|
|
263
|
-
const [isUnderline, setIsUnderline] = useState(false);
|
|
264
|
-
//const [isStrikethrough, setIsStrikethrough] = useState(false);
|
|
265
|
-
const [linkUrl, setLinkUrl] = useState<string>("https://");
|
|
266
|
-
const [targetAttribute, setTargetAttribute] = useState<string>("_self");
|
|
267
|
-
const [classNamesList, setClassNamesList] = useState<Array<string>>(["primary"]);
|
|
268
|
-
|
|
269
|
-
const updateToolbar = useCallback(() => {
|
|
270
|
-
const selection = $getSelection();
|
|
271
|
-
if ($isRangeSelection(selection)) {
|
|
272
|
-
const anchorNode = selection.anchor.getNode();
|
|
273
|
-
const element = anchorNode.getKey() === "root" ? anchorNode : anchorNode.getTopLevelElementOrThrow();
|
|
274
|
-
const elementKey = element.getKey();
|
|
275
|
-
const elementDOM = editor.getElementByKey(elementKey);
|
|
276
|
-
if (elementDOM !== null) {
|
|
277
|
-
setSelectedElementKey(elementKey);
|
|
278
|
-
if ($isListNode(element)) {
|
|
279
|
-
const parentList = $getNearestNodeOfType(anchorNode, ListNode);
|
|
280
|
-
const type = parentList ? parentList.getTag() : element.getTag();
|
|
281
|
-
setBlockType(type);
|
|
282
|
-
} else {
|
|
283
|
-
const type = $isHeadingNode(element) ? element.getTag() : element.getType();
|
|
284
|
-
setBlockType(type);
|
|
285
|
-
if ($isCodeNode(element)) {
|
|
286
|
-
setCodeLanguage(element.getLanguage() || getDefaultCodeLanguage());
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// Update text format
|
|
291
|
-
setIsBold(selection.hasFormat("bold"));
|
|
292
|
-
setIsItalic(selection.hasFormat("italic"));
|
|
293
|
-
setIsUnderline(selection.hasFormat("underline"));
|
|
294
|
-
//setIsStrikethrough(selection.hasFormat("strikethrough"));
|
|
295
|
-
|
|
296
|
-
// Update links
|
|
297
|
-
const node = getSelectedNode(selection);
|
|
298
|
-
const parent = node.getParent();
|
|
299
|
-
if ($isCustomLinkNode(parent) || $isCustomLinkNode(node)) {
|
|
300
|
-
setIsLink(true);
|
|
301
|
-
} else {
|
|
302
|
-
setIsLink(false);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}, [editor]);
|
|
306
|
-
|
|
307
|
-
useEffect(() =>
|
|
308
|
-
mergeRegister(
|
|
309
|
-
editor.registerUpdateListener(({ editorState }) => {
|
|
310
|
-
editorState.read(() => {
|
|
311
|
-
updateToolbar();
|
|
312
|
-
});
|
|
313
|
-
}),
|
|
314
|
-
editor.registerCommand(
|
|
315
|
-
SELECTION_CHANGE_COMMAND,
|
|
316
|
-
(_payload, newEditor) => {
|
|
317
|
-
updateToolbar();
|
|
318
|
-
return false;
|
|
319
|
-
},
|
|
320
|
-
LowPriority
|
|
321
|
-
)
|
|
322
|
-
), [editor, updateToolbar]);
|
|
323
|
-
|
|
324
|
-
const codeLanguges = useMemo(() => getCodeLanguages(), []);
|
|
325
|
-
const onCodeLanguageSelect = useCallback(
|
|
326
|
-
(e: any) => {
|
|
327
|
-
editor.update(() => {
|
|
328
|
-
if (selectedElementKey !== null) {
|
|
329
|
-
const node = $getNodeByKey(selectedElementKey);
|
|
330
|
-
if ($isCodeNode(node)) {
|
|
331
|
-
node.setLanguage(e.target.value);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
},
|
|
336
|
-
[editor, selectedElementKey]
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const insertLink = useCallback(() => {
|
|
340
|
-
editor.dispatchCommand(TOGGLE_CUSTOM_LINK_NODE_COMMAND, {
|
|
341
|
-
url: linkUrl,
|
|
342
|
-
classNames: classNamesList,
|
|
343
|
-
target: targetAttribute
|
|
344
|
-
});
|
|
345
|
-
}, [editor, isLink]); //eslint-disable-line
|
|
346
|
-
|
|
347
|
-
return (
|
|
348
|
-
<div className="toolbar" ref={toolbarRef}>
|
|
349
|
-
{supportedBlockTypes.has(blockType) && (
|
|
350
|
-
<>
|
|
351
|
-
<button
|
|
352
|
-
className="toolbar-item block-controls"
|
|
353
|
-
onClick={() => setShowBlockOptionsDropDown(!showBlockOptionsDropDown)}
|
|
354
|
-
aria-label="Formatting Options"
|
|
355
|
-
>
|
|
356
|
-
<span className={"icon block-type " + blockType} />
|
|
357
|
-
<span className="text">{
|
|
358
|
-
blockTypeToBlockName[blockType]
|
|
359
|
-
}</span>
|
|
360
|
-
<i className="chevron-down" />
|
|
361
|
-
</button>
|
|
362
|
-
{showBlockOptionsDropDown
|
|
363
|
-
&& createPortal(
|
|
364
|
-
<BlockOptionsDropdownList
|
|
365
|
-
editor={editor}
|
|
366
|
-
blockType={blockType}
|
|
367
|
-
toolbarRef={toolbarRef}
|
|
368
|
-
setShowBlockOptionsDropDown={setShowBlockOptionsDropDown}
|
|
369
|
-
/>,
|
|
370
|
-
document.body
|
|
371
|
-
)}
|
|
372
|
-
<Divider />
|
|
373
|
-
</>
|
|
374
|
-
)}
|
|
375
|
-
{blockType === "code"
|
|
376
|
-
? (<>
|
|
377
|
-
<Select className="toolbar-item code-language" onChange={onCodeLanguageSelect} options={codeLanguges} value={codeLanguage} />
|
|
378
|
-
<i className="chevron-down inside" />
|
|
379
|
-
</>)
|
|
380
|
-
: (<>
|
|
381
|
-
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold"); }} className={"toolbar-item spaced " + (isBold ? "active" : "")} aria-label="Format Bold">
|
|
382
|
-
<i className="format bold" />
|
|
383
|
-
</button>
|
|
384
|
-
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic"); }} className={"toolbar-item spaced " + (isItalic ? "active" : "")} aria-label="Format Italics">
|
|
385
|
-
<i className="format italic" />
|
|
386
|
-
</button>
|
|
387
|
-
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline"); }} className={"toolbar-item spaced " + (isUnderline ? "active" : "")} aria-label="Format Underline">
|
|
388
|
-
<i className="format underline" />
|
|
389
|
-
</button>
|
|
390
|
-
<button onClick={insertLink} className={"toolbar-item spaced " + (isLink ? "active" : "")} aria-label="Insert Link">
|
|
391
|
-
<i className="format link" />
|
|
392
|
-
</button>
|
|
393
|
-
{isLink && createPortal(<FloatingLinkEditor selectedElementKey={selectedElementKey} linkUrl={linkUrl} setLinkUrl={setLinkUrl} classNamesList={classNamesList} setClassNamesList={setClassNamesList} targetAttribute={targetAttribute} setTargetAttribute={setTargetAttribute} />, document.body)}
|
|
394
|
-
</>)}
|
|
395
|
-
<Divider />
|
|
396
|
-
<button onClick={() => { props.goFullScreen() }} className="toolbar-item spaced" aria-label="Full Screen">
|
|
397
|
-
<Icon>fullscreen</Icon>
|
|
398
|
-
</button>
|
|
399
|
-
</div>
|
|
400
|
-
);
|
|
401
|
-
}
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { SELECTION_CHANGE_COMMAND, FORMAT_TEXT_COMMAND, $getSelection, $isRangeSelection, $createParagraphNode, $getNodeByKey } from "lexical";
|
|
5
|
+
import { $wrapNodes, $isAtNodeEnd } from "@lexical/selection";
|
|
6
|
+
import { $getNearestNodeOfType, mergeRegister } from "@lexical/utils";
|
|
7
|
+
import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, $isListNode, ListNode } from "@lexical/list";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
9
|
+
import { $createHeadingNode, $createQuoteNode, $isHeadingNode } from "@lexical/rich-text";
|
|
10
|
+
import { $isCodeNode, getDefaultCodeLanguage, getCodeLanguages } from "@lexical/code";
|
|
11
|
+
import { Icon } from "@mui/material";
|
|
12
|
+
import FloatingLinkEditor from "./customLink/FloatingLinkEditor";
|
|
13
|
+
import { TOGGLE_CUSTOM_LINK_NODE_COMMAND, $isCustomLinkNode } from "./customLink/CustomLinkNode";
|
|
14
|
+
|
|
15
|
+
const LowPriority = 1;
|
|
16
|
+
|
|
17
|
+
const supportedBlockTypes = new Set(["paragraph", "quote", "code", "h1", "h2", "h3", "h4", "ul", "ol"]);
|
|
18
|
+
|
|
19
|
+
const blockTypeToBlockName: any = {
|
|
20
|
+
code: "Code Block",
|
|
21
|
+
h1: "Large Heading",
|
|
22
|
+
h2: "Small Heading",
|
|
23
|
+
h3: "Heading",
|
|
24
|
+
h4: "Heading",
|
|
25
|
+
h5: "Heading",
|
|
26
|
+
ol: "Numbered List",
|
|
27
|
+
paragraph: "Normal",
|
|
28
|
+
quote: "Quote",
|
|
29
|
+
ul: "Bulleted List"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function Divider() {
|
|
33
|
+
return <div className="divider" />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
function positionEditorElement(editor: any, rect: any) {
|
|
38
|
+
if (rect === null) {
|
|
39
|
+
editor.style.opacity = "0";
|
|
40
|
+
editor.style.top = "-1000px";
|
|
41
|
+
editor.style.left = "-1000px";
|
|
42
|
+
} else {
|
|
43
|
+
editor.style.opacity = "1";
|
|
44
|
+
editor.style.top = `${rect.top + rect.height + window.pageYOffset + 10}px`;
|
|
45
|
+
editor.style.left = `${rect.left + window.pageXOffset - editor.offsetWidth / 2 + rect.width / 2}px`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
function Select({ onChange, className, options, value }: { onChange: any, className: any, options: any, value: any }) {
|
|
51
|
+
return (
|
|
52
|
+
<select className={className} onChange={onChange} value={value}>
|
|
53
|
+
<option hidden={true} value="" />
|
|
54
|
+
{options.map((option: any) => (
|
|
55
|
+
<option key={option} value={option}>
|
|
56
|
+
{option}
|
|
57
|
+
</option>
|
|
58
|
+
))}
|
|
59
|
+
</select>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getSelectedNode(selection: any) {
|
|
64
|
+
const anchor = selection.anchor;
|
|
65
|
+
const focus = selection.focus;
|
|
66
|
+
const anchorNode = selection.anchor.getNode();
|
|
67
|
+
const focusNode = selection.focus.getNode();
|
|
68
|
+
if (anchorNode === focusNode) {
|
|
69
|
+
return anchorNode;
|
|
70
|
+
}
|
|
71
|
+
const isBackward = selection.isBackward();
|
|
72
|
+
if (isBackward) {
|
|
73
|
+
return $isAtNodeEnd(focus) ? anchorNode : focusNode;
|
|
74
|
+
} else {
|
|
75
|
+
return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function BlockOptionsDropdownList({ editor, blockType, toolbarRef, setShowBlockOptionsDropDown }: { editor: any, blockType: any, toolbarRef: any, setShowBlockOptionsDropDown: any }) {
|
|
80
|
+
const dropDownRef = useRef(null);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const toolbar = toolbarRef.current;
|
|
84
|
+
const dropDown = dropDownRef.current;
|
|
85
|
+
|
|
86
|
+
if (toolbar !== null && dropDown !== null) {
|
|
87
|
+
const { top, left } = toolbar.getBoundingClientRect();
|
|
88
|
+
dropDown.style.top = `${top + 40}px`;
|
|
89
|
+
dropDown.style.left = `${left}px`;
|
|
90
|
+
}
|
|
91
|
+
}, [dropDownRef, toolbarRef]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const dropDown = dropDownRef.current;
|
|
95
|
+
const toolbar = toolbarRef.current;
|
|
96
|
+
|
|
97
|
+
if (dropDown !== null && toolbar !== null) {
|
|
98
|
+
const handle = (event: any) => {
|
|
99
|
+
const target = event.target;
|
|
100
|
+
|
|
101
|
+
if (!dropDown.contains(target) && !toolbar.contains(target)) {
|
|
102
|
+
setShowBlockOptionsDropDown(false);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
document.addEventListener("click", handle);
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
document.removeEventListener("click", handle);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}, [dropDownRef, setShowBlockOptionsDropDown, toolbarRef]);
|
|
112
|
+
|
|
113
|
+
const formatParagraph = () => {
|
|
114
|
+
if (blockType !== "paragraph") {
|
|
115
|
+
editor.update(() => {
|
|
116
|
+
const selection = $getSelection();
|
|
117
|
+
|
|
118
|
+
if ($isRangeSelection(selection)) {
|
|
119
|
+
$wrapNodes(selection, () => $createParagraphNode());
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
setShowBlockOptionsDropDown(false);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const formatLargeHeading = () => {
|
|
127
|
+
if (blockType !== "h1") {
|
|
128
|
+
editor.update(() => {
|
|
129
|
+
const selection = $getSelection();
|
|
130
|
+
|
|
131
|
+
if ($isRangeSelection(selection)) {
|
|
132
|
+
$wrapNodes(selection, () => $createHeadingNode("h1"));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
setShowBlockOptionsDropDown(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const formatSmallHeading = () => {
|
|
140
|
+
if (blockType !== "h2") {
|
|
141
|
+
editor.update(() => {
|
|
142
|
+
const selection = $getSelection();
|
|
143
|
+
|
|
144
|
+
if ($isRangeSelection(selection)) {
|
|
145
|
+
$wrapNodes(selection, () => $createHeadingNode("h2"));
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
setShowBlockOptionsDropDown(false);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const formatHeading3 = () => {
|
|
153
|
+
if (blockType !== "h3") {
|
|
154
|
+
editor.update(() => {
|
|
155
|
+
const selection = $getSelection();
|
|
156
|
+
if ($isRangeSelection(selection)) { $wrapNodes(selection, () => $createHeadingNode("h3")); }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
setShowBlockOptionsDropDown(false);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const formatHeading4 = () => {
|
|
163
|
+
if (blockType !== "h4") {
|
|
164
|
+
editor.update(() => {
|
|
165
|
+
const selection = $getSelection();
|
|
166
|
+
if ($isRangeSelection(selection)) { $wrapNodes(selection, () => $createHeadingNode("h4")); }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
setShowBlockOptionsDropDown(false);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const formatBulletList = () => {
|
|
173
|
+
if (blockType !== "ul") {
|
|
174
|
+
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND);
|
|
175
|
+
} else {
|
|
176
|
+
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
|
177
|
+
}
|
|
178
|
+
setShowBlockOptionsDropDown(false);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const formatNumberedList = () => {
|
|
182
|
+
if (blockType !== "ol") {
|
|
183
|
+
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND);
|
|
184
|
+
} else {
|
|
185
|
+
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
|
186
|
+
}
|
|
187
|
+
setShowBlockOptionsDropDown(false);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const formatQuote = () => {
|
|
191
|
+
if (blockType !== "quote") {
|
|
192
|
+
editor.update(() => {
|
|
193
|
+
const selection = $getSelection();
|
|
194
|
+
|
|
195
|
+
if ($isRangeSelection(selection)) {
|
|
196
|
+
$wrapNodes(selection, () => $createQuoteNode());
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
setShowBlockOptionsDropDown(false);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div className="dropdown" ref={dropDownRef}>
|
|
205
|
+
<button className="item" onClick={formatParagraph}>
|
|
206
|
+
<span className="icon paragraph" />
|
|
207
|
+
<span className="text">Normal</span>
|
|
208
|
+
{blockType === "paragraph" && <span className="active" />}
|
|
209
|
+
</button>
|
|
210
|
+
<button className="item" onClick={formatLargeHeading}>
|
|
211
|
+
<span className="icon large-heading" />
|
|
212
|
+
<span className="text">Large Heading</span>
|
|
213
|
+
{blockType === "h1" && <span className="active" />}
|
|
214
|
+
</button>
|
|
215
|
+
<button className="item" onClick={formatSmallHeading}>
|
|
216
|
+
<span className="icon small-heading" />
|
|
217
|
+
<span className="text">Small Heading</span>
|
|
218
|
+
{blockType === "h2" && <span className="active" />}
|
|
219
|
+
</button>
|
|
220
|
+
<button className="item" onClick={formatHeading3}>
|
|
221
|
+
<span className="icon h3" />
|
|
222
|
+
<span className="text">Heading 3</span>
|
|
223
|
+
{blockType === "h4" && <span className="active" />}
|
|
224
|
+
</button>
|
|
225
|
+
<button className="item" onClick={formatHeading4}>
|
|
226
|
+
<span className="icon h4" />
|
|
227
|
+
<span className="text">Heading 4</span>
|
|
228
|
+
{blockType === "h4" && <span className="active" />}
|
|
229
|
+
</button>
|
|
230
|
+
<button className="item" onClick={formatBulletList}>
|
|
231
|
+
<span className="icon bullet-list" />
|
|
232
|
+
<span className="text">Bullet List</span>
|
|
233
|
+
{blockType === "ul" && <span className="active" />}
|
|
234
|
+
</button>
|
|
235
|
+
<button className="item" onClick={formatNumberedList}>
|
|
236
|
+
<span className="icon numbered-list" />
|
|
237
|
+
<span className="text">Numbered List</span>
|
|
238
|
+
{blockType === "ol" && <span className="active" />}
|
|
239
|
+
</button>
|
|
240
|
+
<button className="item" onClick={formatQuote}>
|
|
241
|
+
<span className="icon quote" />
|
|
242
|
+
<span className="text">Quote</span>
|
|
243
|
+
{blockType === "quote" && <span className="active" />}
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface Props {
|
|
250
|
+
goFullScreen: () => void
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function ToolbarPlugin(props: Props) {
|
|
254
|
+
const [editor] = useLexicalComposerContext();
|
|
255
|
+
const toolbarRef = useRef(null);
|
|
256
|
+
const [blockType, setBlockType] = useState("paragraph");
|
|
257
|
+
const [selectedElementKey, setSelectedElementKey] = useState(null);
|
|
258
|
+
const [showBlockOptionsDropDown, setShowBlockOptionsDropDown] = useState(false);
|
|
259
|
+
const [codeLanguage, setCodeLanguage] = useState("");
|
|
260
|
+
const [isLink, setIsLink] = useState(false);
|
|
261
|
+
const [isBold, setIsBold] = useState(false);
|
|
262
|
+
const [isItalic, setIsItalic] = useState(false);
|
|
263
|
+
const [isUnderline, setIsUnderline] = useState(false);
|
|
264
|
+
//const [isStrikethrough, setIsStrikethrough] = useState(false);
|
|
265
|
+
const [linkUrl, setLinkUrl] = useState<string>("https://");
|
|
266
|
+
const [targetAttribute, setTargetAttribute] = useState<string>("_self");
|
|
267
|
+
const [classNamesList, setClassNamesList] = useState<Array<string>>(["primary"]);
|
|
268
|
+
|
|
269
|
+
const updateToolbar = useCallback(() => {
|
|
270
|
+
const selection = $getSelection();
|
|
271
|
+
if ($isRangeSelection(selection)) {
|
|
272
|
+
const anchorNode = selection.anchor.getNode();
|
|
273
|
+
const element = anchorNode.getKey() === "root" ? anchorNode : anchorNode.getTopLevelElementOrThrow();
|
|
274
|
+
const elementKey = element.getKey();
|
|
275
|
+
const elementDOM = editor.getElementByKey(elementKey);
|
|
276
|
+
if (elementDOM !== null) {
|
|
277
|
+
setSelectedElementKey(elementKey);
|
|
278
|
+
if ($isListNode(element)) {
|
|
279
|
+
const parentList = $getNearestNodeOfType(anchorNode, ListNode);
|
|
280
|
+
const type = parentList ? parentList.getTag() : element.getTag();
|
|
281
|
+
setBlockType(type);
|
|
282
|
+
} else {
|
|
283
|
+
const type = $isHeadingNode(element) ? element.getTag() : element.getType();
|
|
284
|
+
setBlockType(type);
|
|
285
|
+
if ($isCodeNode(element)) {
|
|
286
|
+
setCodeLanguage(element.getLanguage() || getDefaultCodeLanguage());
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Update text format
|
|
291
|
+
setIsBold(selection.hasFormat("bold"));
|
|
292
|
+
setIsItalic(selection.hasFormat("italic"));
|
|
293
|
+
setIsUnderline(selection.hasFormat("underline"));
|
|
294
|
+
//setIsStrikethrough(selection.hasFormat("strikethrough"));
|
|
295
|
+
|
|
296
|
+
// Update links
|
|
297
|
+
const node = getSelectedNode(selection);
|
|
298
|
+
const parent = node.getParent();
|
|
299
|
+
if ($isCustomLinkNode(parent) || $isCustomLinkNode(node)) {
|
|
300
|
+
setIsLink(true);
|
|
301
|
+
} else {
|
|
302
|
+
setIsLink(false);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}, [editor]);
|
|
306
|
+
|
|
307
|
+
useEffect(() =>
|
|
308
|
+
mergeRegister(
|
|
309
|
+
editor.registerUpdateListener(({ editorState }) => {
|
|
310
|
+
editorState.read(() => {
|
|
311
|
+
updateToolbar();
|
|
312
|
+
});
|
|
313
|
+
}),
|
|
314
|
+
editor.registerCommand(
|
|
315
|
+
SELECTION_CHANGE_COMMAND,
|
|
316
|
+
(_payload, newEditor) => {
|
|
317
|
+
updateToolbar();
|
|
318
|
+
return false;
|
|
319
|
+
},
|
|
320
|
+
LowPriority
|
|
321
|
+
)
|
|
322
|
+
), [editor, updateToolbar]);
|
|
323
|
+
|
|
324
|
+
const codeLanguges = useMemo(() => getCodeLanguages(), []);
|
|
325
|
+
const onCodeLanguageSelect = useCallback(
|
|
326
|
+
(e: any) => {
|
|
327
|
+
editor.update(() => {
|
|
328
|
+
if (selectedElementKey !== null) {
|
|
329
|
+
const node = $getNodeByKey(selectedElementKey);
|
|
330
|
+
if ($isCodeNode(node)) {
|
|
331
|
+
node.setLanguage(e.target.value);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
[editor, selectedElementKey]
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const insertLink = useCallback(() => {
|
|
340
|
+
editor.dispatchCommand(TOGGLE_CUSTOM_LINK_NODE_COMMAND, {
|
|
341
|
+
url: linkUrl,
|
|
342
|
+
classNames: classNamesList,
|
|
343
|
+
target: targetAttribute
|
|
344
|
+
});
|
|
345
|
+
}, [editor, isLink]); //eslint-disable-line
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div className="toolbar" ref={toolbarRef}>
|
|
349
|
+
{supportedBlockTypes.has(blockType) && (
|
|
350
|
+
<>
|
|
351
|
+
<button
|
|
352
|
+
className="toolbar-item block-controls"
|
|
353
|
+
onClick={() => setShowBlockOptionsDropDown(!showBlockOptionsDropDown)}
|
|
354
|
+
aria-label="Formatting Options"
|
|
355
|
+
>
|
|
356
|
+
<span className={"icon block-type " + blockType} />
|
|
357
|
+
<span className="text">{
|
|
358
|
+
blockTypeToBlockName[blockType]
|
|
359
|
+
}</span>
|
|
360
|
+
<i className="chevron-down" />
|
|
361
|
+
</button>
|
|
362
|
+
{showBlockOptionsDropDown
|
|
363
|
+
&& createPortal(
|
|
364
|
+
<BlockOptionsDropdownList
|
|
365
|
+
editor={editor}
|
|
366
|
+
blockType={blockType}
|
|
367
|
+
toolbarRef={toolbarRef}
|
|
368
|
+
setShowBlockOptionsDropDown={setShowBlockOptionsDropDown}
|
|
369
|
+
/>,
|
|
370
|
+
document.body
|
|
371
|
+
)}
|
|
372
|
+
<Divider />
|
|
373
|
+
</>
|
|
374
|
+
)}
|
|
375
|
+
{blockType === "code"
|
|
376
|
+
? (<>
|
|
377
|
+
<Select className="toolbar-item code-language" onChange={onCodeLanguageSelect} options={codeLanguges} value={codeLanguage} />
|
|
378
|
+
<i className="chevron-down inside" />
|
|
379
|
+
</>)
|
|
380
|
+
: (<>
|
|
381
|
+
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold"); }} className={"toolbar-item spaced " + (isBold ? "active" : "")} aria-label="Format Bold">
|
|
382
|
+
<i className="format bold" />
|
|
383
|
+
</button>
|
|
384
|
+
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic"); }} className={"toolbar-item spaced " + (isItalic ? "active" : "")} aria-label="Format Italics">
|
|
385
|
+
<i className="format italic" />
|
|
386
|
+
</button>
|
|
387
|
+
<button onClick={() => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline"); }} className={"toolbar-item spaced " + (isUnderline ? "active" : "")} aria-label="Format Underline">
|
|
388
|
+
<i className="format underline" />
|
|
389
|
+
</button>
|
|
390
|
+
<button onClick={insertLink} className={"toolbar-item spaced " + (isLink ? "active" : "")} aria-label="Insert Link">
|
|
391
|
+
<i className="format link" />
|
|
392
|
+
</button>
|
|
393
|
+
{isLink && createPortal(<FloatingLinkEditor selectedElementKey={selectedElementKey} linkUrl={linkUrl} setLinkUrl={setLinkUrl} classNamesList={classNamesList} setClassNamesList={setClassNamesList} targetAttribute={targetAttribute} setTargetAttribute={setTargetAttribute} />, document.body)}
|
|
394
|
+
</>)}
|
|
395
|
+
<Divider />
|
|
396
|
+
<button onClick={() => { props.goFullScreen() }} className="toolbar-item spaced" aria-label="Full Screen">
|
|
397
|
+
<Icon>fullscreen</Icon>
|
|
398
|
+
</button>
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
}
|