mui-extended
Version:
Extended UI Components built on Material UI
381 lines (380 loc) • 21.6 kB
JavaScript
import { __assign, __rest, __spreadArray } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
import CalendarViewMonthIcon from "@mui/icons-material/CalendarViewMonth";
import CodeIcon from "@mui/icons-material/Code";
import FormatBoldIcon from "@mui/icons-material/FormatBold";
import FormatIndentDecreaseIcon from "@mui/icons-material/FormatIndentDecrease";
import FormatIndentIncreaseIcon from "@mui/icons-material/FormatIndentIncrease";
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered";
import FormatQuoteIcon from "@mui/icons-material/FormatQuote";
import LinkIcon from "@mui/icons-material/Link";
import PlaylistAddCheckIcon from "@mui/icons-material/PlaylistAddCheck";
import StrikethroughSIcon from "@mui/icons-material/StrikethroughS";
import TitleIcon from "@mui/icons-material/Title";
import { Box, ButtonGroup, Divider, Grid, IconButton, Tab, Tabs, TextareaAutosize, Tooltip } from "@mui/material";
import { format } from "prettier";
import markdownParser from "prettier/parser-markdown";
import { createRef, forwardRef, Fragment, useState } from "react";
import { useMobile } from "../utils/useMobile";
import InlineCodeIcon from "./icons/inlineCode";
import { MarkdownPreview } from "./Preview";
export var MarkdownEditorMenuButton = function (_a) {
var children = _a.children, title = _a.title, props = __rest(_a, ["children", "title"]);
return (_jsx(Tooltip, { title: title, arrow: true, placement: "top", children: _jsx("span", { children: _jsx(IconButton, __assign({}, props, { children: children })) }) }));
};
var defaultMenu = [
["bold", "italic", "strikethrough"],
["title", "quote"],
["link", "image"],
["code", "inlineCode"],
["unorderedList", "orderedList", "taskList"],
["indentIncrease", "indentDecrease"],
["table"],
["format"]
];
var DefaultButtons = {
bold: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Bold" }, props, { children: _jsx(FormatBoldIcon, {}) }))); },
italic: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Italic" }, props, { children: _jsx(FormatItalicIcon, {}) }))); },
strikethrough: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Strikethrough" }, props, { children: _jsx(StrikethroughSIcon, {}) }))); },
quote: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Quote" }, props, { children: _jsx(FormatQuoteIcon, {}) }))); },
link: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Link" }, props, { children: _jsx(LinkIcon, {}) }))); },
image: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Image" }, props, { children: _jsx(AddPhotoAlternateIcon, {}) }))); },
code: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Code" }, props, { children: _jsx(CodeIcon, {}) }))); },
inlineCode: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Inline Code" }, props, { children: _jsx(InlineCodeIcon, {}) }))); },
title: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Title" }, props, { children: _jsx(TitleIcon, {}) }))); },
unorderedList: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Unordered List" }, props, { children: _jsx(FormatListBulletedIcon, {}) }))); },
orderedList: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Ordered List" }, props, { children: _jsx(FormatListNumberedIcon, {}) }))); },
taskList: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Task List" }, props, { children: _jsx(PlaylistAddCheckIcon, {}) }))); },
indentIncrease: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Increase Indent" }, props, { children: _jsx(FormatIndentIncreaseIcon, {}) }))); },
indentDecrease: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Decrease Indent" }, props, { children: _jsx(FormatIndentDecreaseIcon, {}) }))); },
table: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Table" }, props, { children: _jsx(CalendarViewMonthIcon, {}) }))); },
format: function (props) { return (_jsx(MarkdownEditorMenuButton, __assign({ title: "Format" }, props, { children: _jsx(AutoFixHighIcon, {}) }))); }
};
var getSelectedChunk = function (content, selectionStart, selectionEnd) {
var start = content.substring(0, selectionStart);
var selected = content.substring(selectionStart, selectionEnd);
var end = content.substring(selectionEnd);
return { start: start, selected: selected, end: end };
};
var getSelectedLines = function (content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var startLines = start.split("\n");
var selectedLines = selected.split("\n");
var endLines = end.split("\n");
var unselectedStart = startLines.pop();
var unselectedEnd = endLines.shift();
selectedLines[0] = unselectedStart + selectedLines[0];
var lastSelectedLineIndex = selectedLines.length - 1;
selectedLines[lastSelectedLineIndex] =
selectedLines[lastSelectedLineIndex] + unselectedEnd;
return { start: startLines, selected: selectedLines, end: endLines };
};
var defaultActions = {
bold: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "**").concat(selected, "**").concat(end),
selectionStart: selectionStart + 2,
selectionEnd: selectionEnd + 2
};
},
italic: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "_").concat(selected, "_").concat(end),
selectionStart: selectionStart + 1,
selectionEnd: selectionEnd + 1
};
},
strikethrough: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "~~").concat(selected, "~~").concat(end),
selectionStart: selectionStart + 2,
selectionEnd: selectionEnd + 2
};
},
quote: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l) { return "> " + l; }), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 2,
selectionEnd: selectionEnd + selected.length * 2
};
},
link: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "[").concat(selected, "]()").concat(end),
selectionStart: selectionEnd + 3,
selectionEnd: selectionEnd + 3
};
},
image: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "![").concat(selected, "]()").concat(end),
selectionStart: selectionEnd + 4,
selectionEnd: selectionEnd + 4
};
},
code: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], start, true), [
"```"
], false), selected.map(function (l) { return " " + l; }), true), [
"```"
], false), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 6,
selectionEnd: selectionEnd + selected.length * 2 + 4
};
},
inlineCode: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedChunk(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
return {
content: "".concat(start, "`").concat(selected, "`").concat(end),
selectionStart: selectionStart + 1,
selectionEnd: selectionEnd + 1
};
},
title: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
selected[0] = (selected[0].startsWith("#") ? "#" : "# ") + selected[0];
return {
content: __spreadArray(__spreadArray(__spreadArray([], start, true), selected, true), end, true).join("\n"),
selectionStart: selectionStart + (selected[0].startsWith("##") ? 1 : 2),
selectionEnd: selectionEnd + (selected[0].startsWith("##") ? 1 : 2)
};
},
unorderedList: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l) { return "- " + l; }), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 2,
selectionEnd: selectionEnd + selected.length * 2
};
},
orderedList: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l, i) { return i + 1 + (". " + l); }), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 3,
selectionEnd: selectionEnd + selected.length * 3
};
},
taskList: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l) { return "- [ ] " + l; }), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 6,
selectionEnd: selectionEnd + selected.length * 6
};
},
indentIncrease: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l) { return " " + l; }), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + 2,
selectionEnd: selectionEnd + selected.length * 2
};
},
indentDecrease: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var noOfDeletedSpaces = 0;
var noOfDeletedSpacesInFirstLine = 0;
var newContent = __spreadArray(__spreadArray(__spreadArray([], start, true), selected.map(function (l, i) {
if (l.startsWith(" ")) {
l = l.substring(2);
noOfDeletedSpaces += 2;
if (i == 0) {
noOfDeletedSpacesInFirstLine = 2;
}
}
return l;
}), true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart + noOfDeletedSpacesInFirstLine,
selectionEnd: selectionEnd + noOfDeletedSpaces
};
},
table: function (name, content, selectionStart, selectionEnd) {
var _a = getSelectedLines(content, selectionStart, selectionEnd), start = _a.start, selected = _a.selected, end = _a.end;
var firstSelectedLine = selected.shift();
var newContent = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], start, true), [
firstSelectedLine,
"| Column1 | Column2 |",
"| -------------- | -------------- |",
"| value 1 | value2 |"
], false), selected, true), end, true).join("\n");
return {
content: newContent,
selectionStart: selectionStart,
selectionEnd: selectionStart
};
},
format: function (name, content) {
var newContent = format(content, {
parser: "markdown",
plugins: [markdownParser],
arrowParens: "avoid",
endOfLine: "lf",
trailingComma: "none",
tabWidth: 2
});
return {
content: newContent,
selectionStart: 0,
selectionEnd: 0
};
}
};
export var MarkdownEditorMenu = function (_a) {
var onClick = _a.onClick, menu = _a.menu, menuButtons = _a.menuButtons, disabled = _a.disabled;
var _menu = menu || defaultMenu;
var _menuButtons = __assign(__assign({}, DefaultButtons), menuButtons);
return (_jsx(Box, { sx: { display: "flex", alignItems: "center", flexWrap: "wrap" }, children: _menu.map(function (menuGroup, i) { return (_jsxs(Fragment, { children: [i != 0 ? (_jsx(Divider, { orientation: "vertical", flexItem: true, variant: "middle" })) : null, _jsx(ButtonGroup, { children: menuGroup.map(function (menuItem, j) {
var MenuItemComponent = _menuButtons[menuItem];
var _onClick = function () {
if (!disabled) {
onClick(menuItem);
}
};
return (_jsx(MenuItemComponent, { onClick: _onClick, disabled: disabled }, j));
}) })] }, i)); }) }));
};
export var MarkdownEditorHeader = function (_a) {
var hideTabs = _a.hideTabs, selectedView = _a.selectedView, onViewChange = _a.onViewChange, menuProps = __rest(_a, ["hideTabs", "selectedView", "onViewChange"]);
var handleChange = function (event, newValue) {
onViewChange(newValue == 0 ? "write" : "preview");
};
// TODO: create Tabs with a themable size
return (_jsxs(Grid, { container: true, children: [!hideTabs ? (_jsx(Grid, { item: true, flexGrow: 1, children: _jsxs(Tabs, { value: selectedView == "write" ? 0 : 1, onChange: handleChange, sx: { minHeight: 36 }, children: [_jsx(Tab, { label: "Write", sx: { minHeight: 36, p: 1 } }), _jsx(Tab, { label: "Preview", sx: { minHeight: 36, p: 1 } })] }) })) : null, hideTabs || selectedView == "write" ? (_jsx(Grid, { item: true, children: _jsx(MarkdownEditorMenu, __assign({}, menuProps)) })) : null] }));
};
export var MarkdownEditorContent = forwardRef(function MarkdownEditorContent(_a, ref) {
var write = _a.write, preview = _a.preview, value = _a.value, props = __rest(_a, ["write", "preview", "value"]);
var style = __assign(__assign({}, props.style), { width: "100%", resize: "none", boxSizing: "border-box" });
return (_jsxs(Grid, { container: true, children: [_jsx(Grid, { item: true, xs: 12, sx: { display: write ? "initial" : "none" }, children: _jsx(TextareaAutosize, __assign({ minRows: 10, maxRows: 20 }, props, { value: value, style: style, ref: ref })) }), preview ? (_jsx(Grid, { item: true, xs: 12, children: _jsx(MarkdownPreview, { children: value }) })) : null] }));
});
var createSyntheticEvent = function (event) {
var isDefaultPrevented = false;
var isPropagationStopped = false;
var preventDefault = function () {
isDefaultPrevented = true;
event.preventDefault();
};
var stopPropagation = function () {
isPropagationStopped = true;
event.stopPropagation();
};
return {
nativeEvent: event,
currentTarget: event.currentTarget,
target: event.target,
bubbles: event.bubbles,
cancelable: event.cancelable,
defaultPrevented: event.defaultPrevented,
eventPhase: event.eventPhase,
isTrusted: event.isTrusted,
preventDefault: preventDefault,
isDefaultPrevented: function () { return isDefaultPrevented; },
stopPropagation: stopPropagation,
isPropagationStopped: function () { return isPropagationStopped; },
persist: function () {
// don't do anythiung
},
timeStamp: event.timeStamp,
type: event.type
};
};
var createChangeEvent = function (target) {
var event;
if (typeof InputEvent == "function") {
event = new InputEvent("change", { bubbles: true });
}
else {
event = document.createEvent("InputEvent");
event.initEvent("change", true, true);
}
Object.defineProperty(event, "target", { writable: false, value: target });
var syntheticEvent = createSyntheticEvent(event);
return syntheticEvent;
};
/**
* RTE editor for markdown content
*
* can be used safely within the TextField
* - set `fullWidth` TextField to true
* - `shrink` label in TextField
* - inputRef points to Grid
*
* ```
* <TextField
* InputProps={{ inputComponent: MarkdownEditor }}
* InputLabelProps={{ shrink: true }}
* fullWidth
* />
* ```
*
*/
export var MarkdownEditor = forwardRef(function MarkdownEditor(_a, ref) {
var menu = _a.menu, menuButtons = _a.menuButtons, menuActions = _a.menuActions, alwaysPreview = _a.inlinePreview, onBlur = _a.onBlur, props = __rest(_a, ["menu", "menuButtons", "menuActions", "inlinePreview", "onBlur"]);
var _b = useState("write"), selectedView = _b[0], setSelectedView = _b[1];
var textareaRef = createRef();
var isMobile = useMobile();
var _menuActions = __assign(__assign({}, defaultActions), menuActions);
var onMenuButtonClick = function (name) {
if (textareaRef.current) {
var action = _menuActions[name];
var _a = action(name, textareaRef.current.value, textareaRef.current.selectionStart, textareaRef.current.selectionEnd), content = _a.content, selectionStart = _a.selectionStart, selectionEnd = _a.selectionEnd;
textareaRef.current.value = content;
textareaRef.current.setSelectionRange(selectionStart, selectionEnd);
if (props.onChange) {
var changeEvent = createChangeEvent(textareaRef.current);
props.onChange(changeEvent);
}
}
};
var _onBlur = function (event) {
if (!props.disabled) {
var propagate = true;
var relatedTarget = event.relatedTarget;
if (relatedTarget) {
while (relatedTarget.tagName != "BODY") {
relatedTarget = relatedTarget.parentElement;
if (relatedTarget == event.currentTarget) {
propagate = false; // don't propagate blur event , if clicked within the editor widget
break;
}
}
}
if (propagate) {
if (event.target != textareaRef.current) {
event.target = textareaRef.current;
}
onMenuButtonClick("format");
onBlur(event);
}
}
};
var _onClick = function (event) {
var _a;
event.preventDefault();
event.stopPropagation();
if (!props.disabled) {
(_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // keep textarea focused , when clicked within markdown widget
}
};
return (_jsxs(Grid, { container: true, sx: { p: 1 }, ref: ref, onBlur: _onBlur, onClick: _onClick, children: [_jsx(Grid, { item: true, xs: 12, children: _jsx(MarkdownEditorHeader, { onClick: onMenuButtonClick, hideTabs: isMobile || alwaysPreview, selectedView: selectedView, onViewChange: setSelectedView, menu: menu, menuButtons: menuButtons, disabled: props.disabled }) }), _jsx(Grid, { item: true, xs: 12, children: _jsx(MarkdownEditorContent, __assign({}, props, { write: isMobile || alwaysPreview || selectedView == "write", preview: isMobile || alwaysPreview || selectedView == "preview", ref: textareaRef })) })] }));
});