UNPKG

mui-extended

Version:

Extended UI Components built on Material UI

381 lines (380 loc) 21.6 kB
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 })) })] })); });