diff --git a/packages/blocks-editor/src/Editor/BlocksEditor.tsx b/packages/blocks-editor/src/Editor/BlocksEditor.tsx index 66554ed7d4b..ecdd4cd7e69 100644 --- a/packages/blocks-editor/src/Editor/BlocksEditor.tsx +++ b/packages/blocks-editor/src/Editor/BlocksEditor.tsx @@ -98,7 +98,7 @@ export const BlocksEditor: FunctionComponent = ({
diff --git a/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.scss b/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.scss index 0b4bd4d5778..a71204b5f22 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.scss +++ b/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.scss @@ -1,18 +1,21 @@ .draggable-block-menu { border-radius: 4px; - padding: 2px 1px; + padding: 3px 1px; cursor: grab; opacity: 0; position: absolute; left: 0; top: 0; will-change: transform; + transition: opacity 0.3s; } .draggable-block-menu .icon { - width: 1rem; - height: 1rem; - opacity: 0.4; + width: 0.8rem; + height: 1.1rem; + opacity: 0.2; + padding-left: 4.75px; + padding-top: 2px; } .draggable-block-menu:active { @@ -21,7 +24,6 @@ .draggable-block-menu:hover { background-color: var(--sn-stylekit-contrast-background-color); - padding: 3px; } .draggable-block-target-line { @@ -32,5 +34,6 @@ left: 0; top: 0; opacity: 0; - will-change: transform; + will-change: transform, opacity; + transition: opacity 0.15s; } diff --git a/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.tsx b/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.tsx index e38526bdfd2..f49dfbf3cd1 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.tsx +++ b/packages/blocks-editor/src/Lexical/Plugins/DraggableBlockPlugin/index.tsx @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ +import {$createListNode, $isListNode} from '@lexical/list'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {eventFiles} from '@lexical/rich-text'; import {mergeRegister} from '@lexical/utils'; @@ -19,17 +20,17 @@ import { } from 'lexical'; import {DragEvent as ReactDragEvent, useEffect, useRef, useState} from 'react'; import {createPortal} from 'react-dom'; -import {LexicalDraggableBlockMenu} from '@standardnotes/icons'; +import {BlockIcon} from '@standardnotes/icons'; import {isHTMLElement} from '../../Utils/guard'; import {Point} from '../../Utils/point'; -import {Rect} from '../../Utils/rect'; +import {ContainsPointReturn, Rect} from '../../Utils/rect'; const SPACE = 4; const TARGET_LINE_HALF_HEIGHT = 2; const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu'; const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block'; -const TEXT_BOX_HORIZONTAL_PADDING = 28; +const TEXT_BOX_HORIZONTAL_PADDING = 24; const Downward = 1; const Upward = -1; @@ -53,12 +54,56 @@ function getTopLevelNodeKeys(editor: LexicalEditor): string[] { return root ? root.__children : []; } +function elementContainingEventLocation( + anchorElem: HTMLElement, + element: HTMLElement, + event: MouseEvent, +): {contains: ContainsPointReturn; element: HTMLElement} { + const anchorElementRect = anchorElem.getBoundingClientRect(); + + const eventLocation = new Point(event.x, event.y); + const elementDomRect = Rect.fromDOM(element); + const {marginTop, marginBottom} = window.getComputedStyle(element); + + const rect = elementDomRect.generateNewRect({ + bottom: elementDomRect.bottom + parseFloat(marginBottom), + left: anchorElementRect.left, + right: anchorElementRect.right, + top: elementDomRect.top - parseFloat(marginTop), + }); + + const children = Array.from(element.children); + + const shouldRecurseIntoChildren = ['UL', 'OL', 'LI'].includes( + element.tagName, + ); + + if (shouldRecurseIntoChildren) { + for (const child of children) { + const isLeaf = child.children.length === 0; + if (isLeaf) { + continue; + } + const childResult = elementContainingEventLocation( + anchorElem, + child as HTMLElement, + event, + ); + + if (childResult.contains.result) { + return childResult; + } + } + } + + return {contains: rect.contains(eventLocation), element: element}; +} + function getBlockElement( anchorElem: HTMLElement, editor: LexicalEditor, event: MouseEvent, ): HTMLElement | null { - const anchorElementRect = anchorElem.getBoundingClientRect(); const topLevelNodeKeys = getTopLevelNodeKeys(editor); let blockElem: HTMLElement | null = null; @@ -73,32 +118,22 @@ function getBlockElement( if (elem === null) { break; } - const point = new Point(event.x, event.y); - const domRect = Rect.fromDOM(elem); - const {marginTop, marginBottom} = window.getComputedStyle(elem); - - const rect = domRect.generateNewRect({ - bottom: domRect.bottom + parseFloat(marginBottom), - left: anchorElementRect.left, - right: anchorElementRect.right, - top: domRect.top - parseFloat(marginTop), - }); - - const { - result, - reason: {isOnTopSide, isOnBottomSide}, - } = rect.contains(point); - - if (result) { - blockElem = elem; + const {contains, element} = elementContainingEventLocation( + anchorElem, + elem, + event, + ); + + if (contains.result) { + blockElem = element; prevIndex = index; break; } if (direction === Indeterminate) { - if (isOnTopSide) { + if (contains.reason.isOnTopSide) { direction = Upward; - } else if (isOnBottomSide) { + } else if (contains.reason.isOnBottomSide) { direction = Downward; } else { // stop search block element @@ -124,7 +159,6 @@ function setMenuPosition( ) { if (!targetElem) { floatingElem.style.opacity = '0'; - floatingElem.style.transform = 'translate(-10000px, -10000px)'; return; } @@ -186,13 +220,12 @@ function setTargetLine( targetLineElem.style.width = `${ anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2 }px`; - targetLineElem.style.opacity = '.4'; + targetLineElem.style.opacity = '.6'; } function hideTargetLine(targetLineElem: HTMLElement | null) { if (targetLineElem) { targetLineElem.style.opacity = '0'; - targetLineElem.style.transform = 'translate(-10000px, -10000px)'; } } @@ -284,18 +317,30 @@ function useDraggableBlockMenu( return false; } const targetNode = $getNearestNodeFromDOMNode(targetBlockElem); + if (!targetNode) { return false; } if (targetNode === draggedNode) { return true; } + + let nodeToInsert = draggedNode; + const targetParent = targetNode.getParent(); + const sourceParent = draggedNode.getParent(); + + if ($isListNode(sourceParent) && !$isListNode(targetParent)) { + const newList = $createListNode(sourceParent.getListType()); + newList.append(draggedNode); + nodeToInsert = newList; + } + const {top, height} = targetBlockElem.getBoundingClientRect(); const shouldInsertAfter = pageY - top > height / 2; if (shouldInsertAfter) { - targetNode.insertAfter(draggedNode); + targetNode.insertAfter(nodeToInsert); } else { - targetNode.insertBefore(draggedNode); + targetNode.insertBefore(nodeToInsert); } setDraggableBlockElem(null); @@ -349,7 +394,7 @@ function useDraggableBlockMenu( onDragStart={onDragStart} onDragEnd={onDragEnd}>
- +
diff --git a/packages/blocks-editor/src/Lexical/Plugins/TablePlugin.tsx b/packages/blocks-editor/src/Lexical/Plugins/TablePlugin.tsx index 733157d28a8..554c059a260 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/TablePlugin.tsx +++ b/packages/blocks-editor/src/Lexical/Plugins/TablePlugin.tsx @@ -110,8 +110,8 @@ export function InsertTableDialog({ return ( <> - - + + diff --git a/packages/blocks-editor/src/Lexical/UI/Button.css b/packages/blocks-editor/src/Lexical/UI/Button.css index 946c4dc66c5..f98f623b9e7 100644 --- a/packages/blocks-editor/src/Lexical/UI/Button.css +++ b/packages/blocks-editor/src/Lexical/UI/Button.css @@ -13,13 +13,13 @@ padding-left: 15px; padding-right: 15px; border: 0px; - background-color: #eee; - border-radius: 5px; + background-color: var(--sn-stylekit-contrast-background-color); cursor: pointer; font-size: 14px; } .Button__root:hover { - background-color: #ddd; + background-color: var(--sn-stylekit-info-color); + color: var(--sn-stylekit-info-contrast-color); } .Button__small { padding-top: 5px; @@ -32,5 +32,5 @@ cursor: not-allowed; } .Button__disabled:hover { - background-color: #eee; + background-color: var(--sn-stylekit-secondary-background-color); } diff --git a/packages/blocks-editor/src/Lexical/UI/Input.css b/packages/blocks-editor/src/Lexical/UI/Input.css index 60eb2f1bacd..9f85532e18c 100644 --- a/packages/blocks-editor/src/Lexical/UI/Input.css +++ b/packages/blocks-editor/src/Lexical/UI/Input.css @@ -17,16 +17,17 @@ display: flex; flex: 1; color: #666; + margin-right: 20px; } .Input__input { display: flex; flex: 2; - border: 1px solid #999; + border: 1px solid var(--sn-stylekit-contrast-border-color); + background-color: var(--sn-stylekit-contrast-background-color); padding-top: 7px; padding-bottom: 7px; padding-left: 10px; padding-right: 10px; font-size: 16px; - border-radius: 5px; min-width: 0; } diff --git a/packages/blocks-editor/src/Lexical/UI/Modal.css b/packages/blocks-editor/src/Lexical/UI/Modal.css index 908500bfefc..466700f2b8a 100644 --- a/packages/blocks-editor/src/Lexical/UI/Modal.css +++ b/packages/blocks-editor/src/Lexical/UI/Modal.css @@ -17,7 +17,7 @@ bottom: 0px; left: 0px; right: 0px; - background-color: rgba(40, 40, 40, 0.6); + background-color: rgba(0, 0, 0, 0.7); flex-grow: 0px; flex-shrink: 1px; z-index: 100; @@ -28,22 +28,23 @@ min-width: 300px; display: flex; flex-grow: 0px; - background-color: #fff; + background-color: var(--sn-stylekit-background-color); flex-direction: column; position: relative; - box-shadow: 0 0 20px 0 #444; - border-radius: 10px; + box-shadow: 0 0px 0 var(--sn-stylekit-shadow-color); + border-radius: 0px; } .Modal__title { - color: #444; + color:var(--sn-stylekit-foreground-color); margin: 0px; - padding-bottom: 10px; - border-bottom: 1px solid #ccc; + padding-bottom: 15px; + border-bottom: 1px solid var(--sn-stylekit-border-color); } .Modal__closeButton { border: 0px; position: absolute; right: 20px; + top: 15px; border-radius: 20px; justify-content: center; align-items: center; @@ -52,10 +53,11 @@ height: 30px; text-align: center; cursor: pointer; - background-color: #eee; + background-color: var(--sn-stylekit-contrast-background-color); } .Modal__closeButton:hover { - background-color: #ddd; + background-color: var(--sn-stylekit-info-color); + color: var(--sn-stylekit-info-contrast-color); } .Modal__content { padding-top: 20px; diff --git a/packages/blocks-editor/src/Lexical/UI/Modal.tsx b/packages/blocks-editor/src/Lexical/UI/Modal.tsx index ae7d3e6a631..cfca8a3e3bf 100644 --- a/packages/blocks-editor/src/Lexical/UI/Modal.tsx +++ b/packages/blocks-editor/src/Lexical/UI/Modal.tsx @@ -73,7 +73,7 @@ function PortalImpl({ aria-label="Close modal" type="button" onClick={onClose}> - X + ✕
{children}
diff --git a/packages/icons/src/Icons/ic-block.svg b/packages/icons/src/Icons/ic-block.svg new file mode 100644 index 00000000000..f2c3afaea96 --- /dev/null +++ b/packages/icons/src/Icons/ic-block.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/icons/src/Icons/index.ts b/packages/icons/src/Icons/index.ts index 94a0a3afbf9..d91ae5186ed 100644 --- a/packages/icons/src/Icons/index.ts +++ b/packages/icons/src/Icons/index.ts @@ -21,6 +21,7 @@ import AttachmentFileIcon from './ic-attachment-file.svg' import AuthenticatorIcon from './ic-authenticator.svg' import AuthenticatorVariantIcon from './ic-authenticator-variant.svg' import BackIosIcon from './ic-back-ios.svg' +import BlockIcon from './ic-block.svg' import BlueDotIcon from './blue-dot.svg' import BoldIcon from './ic-bold.svg' import BoxFilledIcon from './ic-box-filled.svg' @@ -224,6 +225,7 @@ export { AuthenticatorIcon, AuthenticatorVariantIcon, BackIosIcon, + BlockIcon, BlueDotIcon, BoldIcon, BoxFilledIcon, diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx index 8a81d669b54..8d17cf4c2fb 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx @@ -11,6 +11,7 @@ import { GetBulletedListBlock } from './Blocks/BulletedList' import { GetChecklistBlock } from './Blocks/Checklist' import { GetDividerBlock } from './Blocks/Divider' import { GetCollapsibleBlock } from './Blocks/Collapsible' +import { GetDynamicPasswordBlocks, GetPasswordBlocks } from './Blocks/Password' import { GetParagraphBlock } from './Blocks/Paragraph' import { GetHeadingsBlocks } from './Blocks/Headings' import { GetQuoteBlock } from './Blocks/Quote' @@ -49,11 +50,15 @@ export default function BlockPickerMenuPlugin(): JSX.Element { GetDividerBlock(editor), ...GetDatetimeBlocks(editor), ...GetAlignmentBlocks(editor), + ...GetPasswordBlocks(editor), GetCollapsibleBlock(editor), ...GetEmbedsBlocks(editor), ] - const dynamicOptions = GetDynamicTableBlocks(editor, queryString || '') + const dynamicOptions = [ + ...GetDynamicTableBlocks(editor, queryString || ''), + ...GetDynamicPasswordBlocks(editor, queryString || ''), + ] return queryString ? [ diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx index ed1735ca43a..f52b15635af 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx @@ -6,17 +6,17 @@ export function GetDatetimeBlocks(editor: LexicalEditor) { return [ new BlockPickerOption('Current date and time', { iconName: 'authenticator', - keywords: ['date'], + keywords: ['date', 'current'], onSelect: () => editor.dispatchCommand(INSERT_DATETIME_COMMAND, 'datetime'), }), new BlockPickerOption('Current time', { iconName: 'authenticator', - keywords: ['time'], + keywords: ['time', 'current'], onSelect: () => editor.dispatchCommand(INSERT_TIME_COMMAND, 'datetime'), }), new BlockPickerOption('Current date', { iconName: 'authenticator', - keywords: ['date'], + keywords: ['date', 'current'], onSelect: () => editor.dispatchCommand(INSERT_DATE_COMMAND, 'datetime'), }), ] diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx new file mode 100644 index 00000000000..06c560c0a3d --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx @@ -0,0 +1,42 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { INSERT_PASSWORD_COMMAND } from '../../Commands' + +const DEFAULT_PASSWORD_LENGTH = 16 +const MIN_PASSWORD_LENGTH = 8 + +export function GetPasswordBlocks(editor: LexicalEditor) { + return [ + new BlockPickerOption('Generate cryptographically secure password', { + iconName: 'password', + keywords: ['password', 'secure'], + onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, String(DEFAULT_PASSWORD_LENGTH)), + }), + ] +} + +export function GetDynamicPasswordBlocks(editor: LexicalEditor, queryString: string) { + if (queryString == null) { + return [] + } + + const lengthRegex = /^\d+$/ + const match = lengthRegex.exec(queryString) + + if (!match) { + return [] + } + + const length = parseInt(match[0], 10) + if (length < MIN_PASSWORD_LENGTH) { + return [] + } + + return [ + new BlockPickerOption(`Generate ${length}-character cryptographically secure password`, { + iconName: 'password', + keywords: ['password', 'secure'], + onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, length.toString()), + }), + ] +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/ClassNames.ts b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/ClassNames.ts index 94b762cc4e7..3dd199e15d0 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/ClassNames.ts +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/ClassNames.ts @@ -1,8 +1,8 @@ import { classNames } from '@/Utils/ConcatenateClassNames' export const PopoverClassNames = classNames( - 'z-dropdown-menu w-full min-w-80', - 'cursor-auto flex-col overflow-y-auto rounded bg-default md:h-auto md:max-w-xs h-auto overflow-y-scroll', + 'z-dropdown-menu w-full', + 'cursor-auto flex-col overflow-y-auto rounded bg-default md:h-auto h-auto overflow-y-scroll', ) export const PopoverItemClassNames = classNames( diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Commands.ts b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Commands.ts index e4541f61a6d..167303fd5ed 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Commands.ts +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Commands.ts @@ -5,3 +5,4 @@ export const INSERT_BUBBLE_COMMAND: LexicalCommand = createCommand('INSE export const INSERT_TIME_COMMAND: LexicalCommand = createCommand('INSERT_TIME_COMMAND') export const INSERT_DATE_COMMAND: LexicalCommand = createCommand('INSERT_DATE_COMMAND') export const INSERT_DATETIME_COMMAND: LexicalCommand = createCommand('INSERT_DATETIME_COMMAND') +export const INSERT_PASSWORD_COMMAND: LexicalCommand = createCommand('INSERT_PASSWORD_COMMAND') diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/Generator.ts b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/Generator.ts new file mode 100644 index 00000000000..07d48f62a59 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/Generator.ts @@ -0,0 +1,26 @@ +const LOWER_CASE_LETTERS = 'abcdefghijklmnopqrstuvwxyz'.split('') +const UPPER_CASE_LETTERS = LOWER_CASE_LETTERS.map((l) => l.toUpperCase()) +const SPECIAL_SYMBOLS = '!£$%^&*()@~:;,./?{}=-_'.split('') +const CHARACTER_SET = [...LOWER_CASE_LETTERS, ...UPPER_CASE_LETTERS, ...SPECIAL_SYMBOLS] +const CHARACTER_SET_LENGTH = CHARACTER_SET.length + +function isValidPassword(password: string) { + const containsSymbols = SPECIAL_SYMBOLS.some((symbol) => password.includes(symbol)) + const containsUpperCase = UPPER_CASE_LETTERS.some((upperLetter) => password.includes(upperLetter)) + const containsLowerCase = LOWER_CASE_LETTERS.some((lowerLetter) => password.includes(lowerLetter)) + + return containsLowerCase && containsUpperCase && containsSymbols +} + +export function generatePassword(length: number): string { + const buffer = new Uint8Array(length) + + let generatedPassword = '' + + do { + window.crypto.getRandomValues(buffer) + generatedPassword = [...buffer].map((x) => CHARACTER_SET[x % CHARACTER_SET_LENGTH]).join('') + } while (!isValidPassword(generatedPassword)) + + return generatedPassword +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/PasswordPlugin.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/PasswordPlugin.tsx new file mode 100644 index 00000000000..4f6ea77c62c --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/PasswordPlugin/PasswordPlugin.tsx @@ -0,0 +1,40 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { + COMMAND_PRIORITY_EDITOR, + $createTextNode, + $getSelection, + $isRangeSelection, + $createParagraphNode, +} from 'lexical' +import { useEffect } from 'react' +import { INSERT_PASSWORD_COMMAND } from '../Commands' +import { mergeRegister } from '@lexical/utils' +import { generatePassword } from './Generator' + +export default function PasswordPlugin(): JSX.Element | null { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + return mergeRegister( + editor.registerCommand( + INSERT_PASSWORD_COMMAND, + (lengthString) => { + const length = Number(lengthString) + const selection = $getSelection() + if (!$isRangeSelection(selection)) { + return false + } + + const paragraph = $createParagraphNode() + const password = generatePassword(length) + paragraph.append($createTextNode(password)) + selection.insertNodes([paragraph]) + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + ) + }, [editor]) + + return null +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx index 0f6a6c5125c..f6cbbd58c30 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx @@ -21,6 +21,7 @@ import { ChangeContentCallbackPlugin, ChangeEditorFunction, } from './Plugins/ChangeContentCallback/ChangeContentCallback' +import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin' const NotePreviewCharLimit = 160 @@ -102,7 +103,7 @@ export const SuperEditor: FunctionComponent = ({ @@ -111,6 +112,7 @@ export const SuperEditor: FunctionComponent = ({ + (changeEditorFunction.current = callback)} diff --git a/packages/web/src/javascripts/Components/Popover/Popover.tsx b/packages/web/src/javascripts/Components/Popover/Popover.tsx index 0d8a12b3ddc..69ff3223dfb 100644 --- a/packages/web/src/javascripts/Components/Popover/Popover.tsx +++ b/packages/web/src/javascripts/Components/Popover/Popover.tsx @@ -93,7 +93,7 @@ const Popover = ({ anchorElement={anchorElement} anchorPoint={anchorPoint} childPopovers={childPopovers} - className={className} + className={`popover-content-container ${className ?? ''}`} id={popoverId.current} overrideZIndex={overrideZIndex} side={side}