UNPKG

phx-react

Version:

PHX REACT

230 lines • 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = ToolbarPlugin; const tslib_1 = require("tslib"); const link_1 = require("@lexical/link"); const list_1 = require("@lexical/list"); const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext"); const rich_text_1 = require("@lexical/rich-text"); const selection_1 = require("@lexical/selection"); const utils_1 = require("@lexical/utils"); const lexical_1 = require("lexical"); const react_1 = require("react"); const React = tslib_1.__importStar(require("react")); const getSelectedNode_1 = require("../../utils/getSelectedNode"); const url_1 = require("../../utils/url"); const heading_1 = tslib_1.__importStar(require("./components/heading")); const font_size_1 = tslib_1.__importDefault(require("./components/font-size")); const bold_text_1 = tslib_1.__importDefault(require("./components/bold-text")); const italic_text_1 = tslib_1.__importDefault(require("./components/italic-text")); const underline_text_1 = tslib_1.__importDefault(require("./components/underline-text")); const text_color_1 = tslib_1.__importDefault(require("./components/text-color")); const text_align_1 = tslib_1.__importDefault(require("./components/text-align")); const bullet_1 = tslib_1.__importDefault(require("./components/bullet")); const number_bullet_1 = tslib_1.__importDefault(require("./components/number-bullet")); const upload_pdf_1 = tslib_1.__importDefault(require("./components/upload-pdf")); const upload_image_1 = tslib_1.__importDefault(require("./components/upload-image")); const link_2 = tslib_1.__importDefault(require("./components/link")); const ImageNode_1 = require("../../nodes/ImageNode"); const InlineImageNode_1 = require("../../nodes/InlineImageNode"); const blockTypeToBlockName = { bullet: 'Bulleted List', check: 'Check List', code: 'Code Block', h1: 'Heading 1', h2: 'Heading 2', h3: 'Heading 3', h4: 'Heading 4', h5: 'Heading 5', h6: 'Heading 6', number: 'Numbered List', paragraph: 'Normal', quote: 'Quote', }; /** * Checks whether a block type is a heading type. * @param value Block type to test. * @returns True when the value is a heading block type. */ function isHeadingBlockType(value) { return value in heading_1.DEFAULT_HEADING_FONT_SIZES; } /** * Gets the fallback block type represented by a list item font size. * @param fontSize Font size from the selected list item. * @returns Matching heading type, or paragraph when there is no match. */ function getBlockTypeFromFontSize(fontSize) { var _a; const headingType = (_a = Object.entries(heading_1.DEFAULT_HEADING_FONT_SIZES).find(([, size]) => size === fontSize)) === null || _a === void 0 ? void 0 : _a[0]; return headingType !== undefined ? headingType : 'paragraph'; } /** * Reads font size from a serialized style string. * @param style Serialized CSS style string. * @returns Font size value when present. */ function getFontSizeFromStyle(style) { return (0, selection_1.getStyleObjectFromCSS)(style)['font-size']; } function DisabledToolbarGroup({ children, disabled, }) { return (React.createElement("div", { className: disabled ? 'relative cursor-not-allowed opacity-40' : undefined }, children, disabled && React.createElement("div", { className: 'absolute inset-0 z-10 cursor-not-allowed' }))); } function ToolbarPlugin({ apiCdnUpload, disabled, }) { const [editor] = (0, LexicalComposerContext_1.useLexicalComposerContext)(); const [activeEditor, setActiveEditor] = (0, react_1.useState)(editor); const [blockType, setBlockType] = (0, react_1.useState)('paragraph'); const [listBlockType, setListBlockType] = (0, react_1.useState)(null); const [fontSize, setFontSize] = (0, react_1.useState)('14px'); const [fontColor, setFontColor] = (0, react_1.useState)('#000'); const [elementFormat, setElementFormat] = (0, react_1.useState)('left'); const [isImageSelected, setIsImageSelected] = (0, react_1.useState)(false); const [isLink, setIsLink] = (0, react_1.useState)(false); const [isBold, setIsBold] = (0, react_1.useState)(false); const [isItalic, setIsItalic] = (0, react_1.useState)(false); const [isUnderline, setIsUnderline] = (0, react_1.useState)(false); const $updateToolbar = (0, react_1.useCallback)(() => { const selection = (0, lexical_1.$getSelection)(); if ((0, lexical_1.$isNodeSelection)(selection)) { const node = selection.getNodes()[0]; if ((0, ImageNode_1.$isImageNode)(node) || (0, InlineImageNode_1.$isInlineImageNode)(node)) { setIsImageSelected(true); setElementFormat((node.getPosition() || 'center')); } else { setIsImageSelected(false); } return; } if ((0, lexical_1.$isRangeSelection)(selection)) { setIsImageSelected(false); const anchorNode = selection.anchor.getNode(); let element = anchorNode.getKey() === 'root' ? anchorNode : (0, utils_1.$findMatchingParent)(anchorNode, (e) => { const parent = e.getParent(); return parent !== null && (0, lexical_1.$isRootOrShadowRoot)(parent); }); if (element === null) { element = anchorNode.getTopLevelElementOrThrow(); } const elementKey = element.getKey(); const elementDOM = activeEditor.getElementByKey(elementKey); // Update text format setIsBold(selection.hasFormat('bold')); setIsItalic(selection.hasFormat('italic')); setIsUnderline(selection.hasFormat('underline')); // Update links const node = (0, getSelectedNode_1.getSelectedNode)(selection); const parent = node.getParent(); if ((0, link_1.$isLinkNode)(parent) || (0, link_1.$isLinkNode)(node)) { setIsLink(true); } else { setIsLink(false); } const defaultFontSize = (0, rich_text_1.$isHeadingNode)(element) ? heading_1.DEFAULT_HEADING_FONT_SIZES[element.getTag()] : '14px'; let selectionFontSize = (0, selection_1.$getSelectionStyleValueForProperty)(selection, 'font-size', defaultFontSize); if (elementDOM !== null) { const parentList = (0, utils_1.$getNearestNodeOfType)(anchorNode, list_1.ListNode); if (parentList !== null || (0, list_1.$isListNode)(element)) { const listItemNode = (0, list_1.$isListItemNode)(anchorNode) ? anchorNode : (0, utils_1.$findMatchingParent)(anchorNode, (node) => (0, list_1.$isListItemNode)(node)); const listItemFontSize = (0, list_1.$isListItemNode)(listItemNode) ? getFontSizeFromStyle(listItemNode.getTextStyle()) : undefined; const listItemBlockType = (0, list_1.$isListItemNode)(listItemNode) ? (0, heading_1.getListItemBlockType)(listItemNode) : null; // @ts-ignore const type = parentList ? parentList.getListType() : element.getListType(); if (listItemFontSize !== undefined && !selection.isCollapsed()) { selectionFontSize = listItemFontSize; } setListBlockType(type); setBlockType(listItemBlockType !== null && listItemBlockType !== void 0 ? listItemBlockType : getBlockTypeFromFontSize(selectionFontSize)); } else { setListBlockType(null); const type = (0, rich_text_1.$isHeadingNode)(element) ? element.getTag() : element.getType(); if (type in blockTypeToBlockName) { setBlockType(type); } } } // Handle buttons setFontSize(selectionFontSize); setFontColor((0, selection_1.$getSelectionStyleValueForProperty)(selection, 'color', '#000')); let matchingParent; if ((0, link_1.$isLinkNode)(parent)) { // If node is a link, we need to fetch the parent paragraph node to set format matchingParent = (0, utils_1.$findMatchingParent)(node, (parentNode) => (0, lexical_1.$isElementNode)(parentNode) && !parentNode.isInline()); } // If matchingParent is a valid node, pass it's format type setElementFormat((0, lexical_1.$isElementNode)(matchingParent) ? matchingParent.getFormatType() : (0, lexical_1.$isElementNode)(node) ? node.getFormatType() : (parent === null || parent === void 0 ? void 0 : parent.getFormatType()) || 'left'); } }, [activeEditor]); (0, react_1.useEffect)(() => activeEditor.registerCommand(lexical_1.SELECTION_CHANGE_COMMAND, (_payload, newEditor) => { $updateToolbar(); setActiveEditor(newEditor); return false; }, lexical_1.COMMAND_PRIORITY_CRITICAL), [activeEditor, $updateToolbar]); (0, react_1.useEffect)(() => (0, utils_1.mergeRegister)(activeEditor.registerUpdateListener(({ editorState }) => { editorState.read(() => { $updateToolbar(); }); })), [$updateToolbar, activeEditor]); (0, react_1.useEffect)(() => activeEditor.registerCommand(lexical_1.KEY_MODIFIER_COMMAND, (payload) => { const event = payload; const { code, ctrlKey, metaKey } = event; if (code === 'KeyK' && (ctrlKey || metaKey)) { event.preventDefault(); return activeEditor.dispatchCommand(link_1.TOGGLE_LINK_COMMAND, (0, url_1.sanitizeUrl)('https://')); } return false; }, lexical_1.COMMAND_PRIORITY_NORMAL), [activeEditor, isLink]); (0, react_1.useEffect)(() => { if (!window.pdfjsLib) { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; document.body.appendChild(script); } }, []); (0, react_1.useEffect)(() => { if (activeEditor) { activeEditor.setEditable(!disabled); } }, [activeEditor, disabled]); const parsedFontSize = parseInt(fontSize, 10); const activeListHeadingTag = listBlockType !== null && isHeadingBlockType(blockType) ? blockType : null; return (React.createElement("div", { className: 'flex relative flex-wrap gap-1 p-2 bg-gray-100 border-b border-gray-300' }, disabled && React.createElement("div", { className: 'cursor-not-allowed z-20 absolute inset-0 bg-gray-200 opacity-20' }), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, blockType in blockTypeToBlockName && activeEditor === editor && (React.createElement(heading_1.default, { value: blockType, editor: activeEditor }))), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(font_size_1.default, { editor: activeEditor, fontSize: Number.isNaN(parsedFontSize) ? null : parsedFontSize, listHeadingTag: activeListHeadingTag })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(bold_text_1.default, { isActive: isBold, editor: activeEditor })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(italic_text_1.default, { isActive: isItalic, editor: activeEditor })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(underline_text_1.default, { isActive: isUnderline, editor: activeEditor })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(text_color_1.default, { editor: activeEditor, color: fontColor })), React.createElement(text_align_1.default, { value: elementFormat, editor: activeEditor, isImageSelected: isImageSelected }), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(bullet_1.default, { isActive: listBlockType === 'bullet', editor: activeEditor })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(number_bullet_1.default, { isActive: listBlockType === 'number', editor: activeEditor })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(upload_pdf_1.default, { editor: activeEditor, apiCdnUpload: apiCdnUpload })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(upload_image_1.default, { editor: activeEditor, apiCdnUpload: apiCdnUpload })), React.createElement(DisabledToolbarGroup, { disabled: isImageSelected }, React.createElement(link_2.default, { editor: activeEditor })))); } //# sourceMappingURL=index.js.map