From 29b7e989a6c80ccc1d4c4957ebdcdeb65772577f Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 22 Dec 2023 16:40:42 +0530 Subject: [PATCH] chore: show Super demo modal if user doesn't have subscription when switching editor to Super --- .../Service/SuperConverterServiceInterface.ts | 10 +- packages/mobile/ios/Podfile.lock | 4 +- .../StandardNotes.xcodeproj/project.pbxproj | 2 + .../ChangeEditor/ChangeEditorMenu.tsx | 7 +- .../ChangeEditor/ChangeEditorMultipleMenu.tsx | 6 + .../Popover/PositionedPopoverContent.tsx | 7 +- .../javascripts/Components/Popover/Types.ts | 1 + .../Utils/getAdjustedStylesForNonPortal.ts | 2 +- .../Panes/General/NewNoteDefaults.tsx | 5 + .../PremiumFeatureModalType.tsx | 1 + .../PremiumFeaturesModal.tsx | 52 ++++-- .../Subviews/SuperDemo.tsx | 64 +++++++ .../Subviews/SuperDemoInitialValue.ts | 164 ++++++++++++++++++ .../Subviews/UpgradePrompt.tsx | 123 +++++++------ .../StyledTooltip/StyledTooltip.tsx | 14 +- .../Components/SuperEditor/BlocksEditor.tsx | 20 ++- .../SuperEditor/BlocksEditorComposer.tsx | 6 +- .../SuperEditor/Lexical/UI/Modal.css | 2 +- .../SuperEditor/Lexical/UI/Modal.tsx | 18 +- .../BlockPickerPlugin/BlockPickerMenuItem.tsx | 2 +- .../BlockPickerPlugin/BlockPickerPlugin.tsx | 3 +- .../Plugins/ToolbarPlugin/ToolbarPlugin.tsx | 25 ++- .../Components/SuperEditor/SuperEditor.tsx | 25 +-- .../Tools/HeadlessSuperConverter.tsx | 14 +- .../Controllers/FeaturesController.ts | 4 + .../src/javascripts/Hooks/usePremiumModal.tsx | 20 ++- 26 files changed, 482 insertions(+), 119 deletions(-) create mode 100644 packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemo.tsx create mode 100644 packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemoInitialValue.ts diff --git a/packages/files/src/Domain/Service/SuperConverterServiceInterface.ts b/packages/files/src/Domain/Service/SuperConverterServiceInterface.ts index 8c9203505..d204f177e 100644 --- a/packages/files/src/Domain/Service/SuperConverterServiceInterface.ts +++ b/packages/files/src/Domain/Service/SuperConverterServiceInterface.ts @@ -1,6 +1,14 @@ export interface SuperConverterServiceInterface { isValidSuperString(superString: string): boolean convertSuperStringToOtherFormat: (superString: string, toFormat: 'txt' | 'md' | 'html' | 'json') => Promise - convertOtherFormatToSuperString: (otherFormatString: string, fromFormat: 'txt' | 'md' | 'html' | 'json') => string + convertOtherFormatToSuperString: ( + otherFormatString: string, + fromFormat: 'txt' | 'md' | 'html' | 'json', + options?: { + html?: { + addLineBreaks?: boolean + } + }, + ) => string getEmbeddedFileIDsFromSuperString(superString: string): string[] } diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 741369608..040690037 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -736,7 +736,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: a7c83b31436843459a1961bfd74b96033dc77234 + boost: 57d2868c099736d80fcd648bf211b4431e51a558 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 71803c074f6325f10b5ec891c443b6bbabef0ca7 @@ -756,7 +756,7 @@ SPEC CHECKSUMS: MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda + RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: df81ab637d35fac9e6eb94611cfd20f0feb05455 RCTTypeSafety: 4636e4a36c7c2df332bda6d59b19b41c443d4287 React: e0cc5197a804031a6c53fb38483c3485fcb9d6f3 diff --git a/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj b/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj index 076e07005..a7662af20 100644 --- a/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj @@ -991,6 +991,7 @@ OTHER_LDFLAGS = ( "$(inherited)", " ", + "-Wl -ld_classic ", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1059,6 +1060,7 @@ OTHER_LDFLAGS = ( "$(inherited)", " ", + "-Wl -ld_classic ", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; diff --git a/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMenu.tsx b/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMenu.tsx index 0e6eef870..32b07f6de 100644 --- a/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMenu.tsx +++ b/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMenu.tsx @@ -154,6 +154,11 @@ const ChangeEditorMenu: FunctionComponent = ({ const handleMenuSelection = useCallback( async (menuItem: EditorMenuItem) => { if (!menuItem.isEntitled) { + if (menuItem.uiFeature.featureIdentifier === NativeFeatureIdentifier.TYPES.SuperEditor) { + premiumModal.showSuperDemo() + return + } + premiumModal.activate(menuItem.uiFeature.displayName) return } @@ -249,7 +254,7 @@ const ChangeEditorMenu: FunctionComponent = ({ <> -
+

Choose a note type

{unableToFindEditor && ( diff --git a/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMultipleMenu.tsx b/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMultipleMenu.tsx index 249e7fe68..115e2c4e5 100644 --- a/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMultipleMenu.tsx +++ b/packages/web/src/javascripts/Components/ChangeEditor/ChangeEditorMultipleMenu.tsx @@ -9,6 +9,7 @@ import { NoteMutator, NoteType, SNNote, + NativeFeatureIdentifier, } from '@standardnotes/snjs' import { useCallback, useMemo, useState } from 'react' import Icon from '../Icon/Icon' @@ -60,6 +61,11 @@ const ChangeEditorMultipleMenu = ({ application, notes, setDisableClickOutside } const handleMenuSelection = useCallback( async (itemToBeSelected: EditorMenuItem) => { if (!itemToBeSelected.isEntitled) { + if (itemToBeSelected.uiFeature.featureIdentifier === NativeFeatureIdentifier.TYPES.SuperEditor) { + premiumModal.showSuperDemo() + return + } + premiumModal.activate(itemToBeSelected.uiFeature.displayName) return } diff --git a/packages/web/src/javascripts/Components/Popover/PositionedPopoverContent.tsx b/packages/web/src/javascripts/Components/Popover/PositionedPopoverContent.tsx index c21ce17b7..5a2191070 100644 --- a/packages/web/src/javascripts/Components/Popover/PositionedPopoverContent.tsx +++ b/packages/web/src/javascripts/Components/Popover/PositionedPopoverContent.tsx @@ -33,6 +33,7 @@ const PositionedPopoverContent = ({ hideOnClickInModal = false, setAnimationElement, containerClassName, + documentElement, }: PopoverContentProps) => { const [popoverElement, setPopoverElement] = useState(null) const popoverRect = useAutoElementRect(popoverElement) @@ -45,13 +46,13 @@ const PositionedPopoverContent = ({ y: anchorPoint?.y, }) const anchorRect = anchorPoint ? anchorPointRect : anchorElementRect - const documentRect = useDocumentRect() + const _documentRect = useDocumentRect() const isDesktopScreen = useMediaQuery(MediaQueryBreakpoints.md) const styles = getPositionedPopoverStyles({ align, anchorRect, - documentRect, + documentRect: documentElement?.getBoundingClientRect() ?? _documentRect, popoverRect: popoverRect ?? popoverElement?.getBoundingClientRect(), side, disableMobileFullscreenTakeover, @@ -73,7 +74,7 @@ const PositionedPopoverContent = ({ let adjustedStyles: PopoverCSSProperties | undefined = undefined if (!portal && popoverElement && styles) { - adjustedStyles = getAdjustedStylesForNonPortalPopover(popoverElement, styles) + adjustedStyles = getAdjustedStylesForNonPortalPopover(popoverElement, styles, documentElement) } usePopoverCloseOnClickOutside({ diff --git a/packages/web/src/javascripts/Components/Popover/Types.ts b/packages/web/src/javascripts/Components/Popover/Types.ts index 0eda11b21..6ada949d1 100644 --- a/packages/web/src/javascripts/Components/Popover/Types.ts +++ b/packages/web/src/javascripts/Components/Popover/Types.ts @@ -50,6 +50,7 @@ type CommonPopoverProps = { offset?: number hideOnClickInModal?: boolean open: boolean + documentElement?: HTMLElement } export type PopoverContentProps = CommonPopoverProps & { diff --git a/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts b/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts index 639bcf9cd..393f11d85 100644 --- a/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts +++ b/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts @@ -6,7 +6,7 @@ export const getAdjustedStylesForNonPortalPopover = ( styles: PopoverCSSProperties, parent?: HTMLElement, ) => { - const absoluteParent = parent || getAbsolutePositionedParent(popoverElement) + const absoluteParent = parent || getAbsolutePositionedParent(popoverElement) || popoverElement.parentElement const translateXProperty = styles?.['--translate-x'] const translateYProperty = styles?.['--translate-y'] diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/General/NewNoteDefaults.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/General/NewNoteDefaults.tsx index da378b86a..f21a44378 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/General/NewNoteDefaults.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/General/NewNoteDefaults.tsx @@ -77,6 +77,11 @@ const NewNoteDefaults = () => { } if (application.features.getFeatureStatus(identifier) !== FeatureStatus.Entitled) { + if (feature.getValue().value === NativeFeatureIdentifier.TYPES.SuperEditor) { + premiumModal.showSuperDemo() + return + } + const editorItem = editorItems.find((item) => item.value === value) if (editorItem) { premiumModal.activate(editorItem.label) diff --git a/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeatureModalType.tsx b/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeatureModalType.tsx index e587b8297..93d764a0a 100644 --- a/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeatureModalType.tsx +++ b/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeatureModalType.tsx @@ -1,4 +1,5 @@ export enum PremiumFeatureModalType { UpgradePrompt, UpgradeSuccess, + SuperDemo, } diff --git a/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeaturesModal.tsx b/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeaturesModal.tsx index ab50d8c17..68ba5ec97 100644 --- a/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeaturesModal.tsx +++ b/packages/web/src/javascripts/Components/PremiumFeaturesModal/PremiumFeaturesModal.tsx @@ -5,6 +5,8 @@ import { FeatureName } from '@/Controllers/FeatureName' import { SuccessPrompt } from './Subviews/SuccessPrompt' import { UpgradePrompt } from './Subviews/UpgradePrompt' import Modal from '../Modal/Modal' +import SuperDemo from './Subviews/SuperDemo' +import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' type Props = { application: WebApplication @@ -23,23 +25,41 @@ const PremiumFeaturesModal: FunctionComponent = ({ }) => { const ctaButtonRef = useRef(null) - return ( - }> -
-
- {type === PremiumFeatureModalType.UpgradePrompt && ( - - )} + const isShowingSuperDemo = type === PremiumFeatureModalType.SuperDemo - {type === PremiumFeatureModalType.UpgradeSuccess && } -
-
+ const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) + + return ( + } + actions={ + isShowingSuperDemo + ? [ + { + label: 'Done', + type: 'primary', + onClick: onClose, + hidden: !isMobileScreen, + mobileSlot: 'right', + }, + ] + : undefined + } + > + {type === PremiumFeatureModalType.UpgradePrompt && ( + + )} + {type === PremiumFeatureModalType.UpgradeSuccess && } + {type === PremiumFeatureModalType.SuperDemo && } ) } diff --git a/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemo.tsx b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemo.tsx new file mode 100644 index 000000000..0e1012ea7 --- /dev/null +++ b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemo.tsx @@ -0,0 +1,64 @@ +import { BlocksEditor } from '@/Components/SuperEditor/BlocksEditor' +import { BlocksEditorComposer } from '@/Components/SuperEditor/BlocksEditorComposer' +import BlockPickerMenuPlugin from '@/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin' +import usePreference from '@/Hooks/usePreference' +import { useResponsiveEditorFontSize } from '@/Utils/getPlaintextFontSize' +import { EditorLineHeightValues, PrefKey, classNames } from '@standardnotes/snjs' +import { CSSProperties, useRef, useState } from 'react' +import { SuperDemoInitialValue } from './SuperDemoInitialValue' +import { UpgradePrompt } from './UpgradePrompt' +import { useApplication } from '@/Components/ApplicationProvider' +import { useAutoElementRect } from '@/Hooks/useElementRect' + +const SuperDemo = ({ hasSubscription, onClose }: { hasSubscription: boolean; onClose: () => void }) => { + const application = useApplication() + + const lineHeight = usePreference(PrefKey.EditorLineHeight) + const fontSize = usePreference(PrefKey.EditorFontSize) + const responsiveFontSize = useResponsiveEditorFontSize(fontSize, false) + + const ctaRef = useRef(null) + + const [demoContainer, setDemoContainer] = useState(null) + const demoContainerRect = useAutoElementRect(demoContainer, { + updateOnWindowResize: true, + }) + + return ( +
+
+ +
+
+ + + + + +
+
+ ) +} + +export default SuperDemo diff --git a/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemoInitialValue.ts b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemoInitialValue.ts new file mode 100644 index 000000000..4d6a00549 --- /dev/null +++ b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/SuperDemoInitialValue.ts @@ -0,0 +1,164 @@ +import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter' + +const InitialHTML = `
+

This is a demo of Super notes

+


+

Super notes are our new rich text experience. With Super notes, you can create rich, dynamic text with powerful options.

+


+

Images

+


+

You can add images to your note by selecting the "Image from URL" option from the / menu or Insert menu in the toolbar.

+


+

+


+

Lists

+


+
    +
  • Type - followed by a space in begin a + list
  • +
  • Type 1. followed by a space in begin a numbered + list
  • +
  • Type [] followed by a space + to begin a checklist
  • +
+


+
    +
  • A list
  • +
  • +
      +
    • Indent the list
    • +
    • +
        +
      • And even more
      • +
      +
    • +
    +
  • +
+


+
    +
  1. A numbered list
  2. +
  3. With multiple levels
  4. +
  5. And even more
  6. +
+


+
    +
  • + Create +
  • +
  • + a +
  • +
  • + checklist +
  • +
+


+

Collapsible sections

+


+
+ Collapsible section +
+

Collapsible sections can include all + other types of content like

+


+

Heading

+


+
    +
  • a list
  • +
+
    +
  1. numbered
  2. +
+
    +
  • check + list +
  • +
+


+
A code block
+


+

You can even nest collapsible + sections.

+


+
+ Nested collapsible section +
+
Quote
+
+
+
+
+


+

Code blocks

+


+

Type \`\`\` followed by a space to create a code + block. You can choose the language when your + cursor is within the code block.

+


+
function main() {
const variable = "string";
return TEST;
}
+


+

Tables

+ + + + + + + + + + + + + + + + + + + + + + + +
+

Header

+
+

Column 1

+
+

Column 2

+
+

Row 1

+
+

Row 1 x Column 1

+
+

Row 1 x Column 2

+
+

Row 2

+
+

Row 2 x Column 1

+
+

Row 2 x Column 2

+
+


+

Passwords

+

You can generate a secure password using + the "Generate password" command using the / + menu.

+


+
    +
  • }:hcMrIFgaijpkyz
  • +
  • *raF/qi$m?y?iiBS
  • +
  • YuVmWf(gOD&=vjbB
  • +
+
` + +export function SuperDemoInitialValue() { + return new HeadlessSuperConverter().convertOtherFormatToSuperString(InitialHTML, 'html', { + html: { + addLineBreaks: false, + }, + }) +} diff --git a/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/UpgradePrompt.tsx b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/UpgradePrompt.tsx index c81e15bc7..ab39b5749 100644 --- a/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/UpgradePrompt.tsx +++ b/packages/web/src/javascripts/Components/PremiumFeaturesModal/Subviews/UpgradePrompt.tsx @@ -2,6 +2,8 @@ import { useCallback } from 'react' import { WebApplication } from '@/Application/WebApplication' import Icon from '@/Components/Icon/Icon' import { PremiumFeatureIconClass, PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon' +import { classNames } from '@standardnotes/snjs' +import { requestCloseAllOpenModalsAndPopovers } from '@/Utils/CloseOpenModalsAndPopovers' type Props = { featureName?: string @@ -12,10 +14,12 @@ type Props = { } & ( | { inline: true + preferHorizontalLayout?: boolean onClose?: never } | { inline?: false + preferHorizontalLayout?: never onClose: () => void } ) @@ -28,11 +32,13 @@ export const UpgradePrompt = ({ onClose, onClick, inline, + preferHorizontalLayout = false, }: Props) => { const handleClick = useCallback(() => { if (onClick) { onClick() } + requestCloseAllOpenModalsAndPopovers() if (hasSubscription && !application.isNativeIOS()) { void application.openSubscriptionDashboard.execute() } else { @@ -44,65 +50,76 @@ export const UpgradePrompt = ({ }, [application, hasSubscription, onClose, onClick]) return ( - <> -
+
+ {!inline && (
- {!inline && ( - - )} + +
+ )} +
+ +
+
+
+ Enable Advanced Features
- + {featureName && ( + + To take advantage of {featureName} and other advanced features, + upgrade your current plan. + + )} + {!featureName && ( + + To take advantage of all the advanced features Standard Notes has to offer, upgrade your current plan. + + )} + {application.isNativeIOS() && ( +
+
The Professional Plan costs $119.99/year and includes benefits like
+
    +
  • 100GB encrypted file storage
  • +
  • + Access to all note types, including Super, markdown, rich text, authenticator, tasks, and spreadsheets +
  • +
  • Access to Daily Notebooks and Moments journals
  • +
  • Note history going back indefinitely
  • +
  • Nested folders for your tags
  • +
  • Premium support
  • +
+
+ )}
-
Enable Advanced Features
-
- {featureName && ( - - To take advantage of {featureName} and other advanced features, - upgrade your current plan. - +
- -
- -
- + ref={ctaRef} + > + Upgrade + +
) } diff --git a/packages/web/src/javascripts/Components/StyledTooltip/StyledTooltip.tsx b/packages/web/src/javascripts/Components/StyledTooltip/StyledTooltip.tsx index 1976fbf21..a1d36a1cd 100644 --- a/packages/web/src/javascripts/Components/StyledTooltip/StyledTooltip.tsx +++ b/packages/web/src/javascripts/Components/StyledTooltip/StyledTooltip.tsx @@ -18,6 +18,7 @@ const StyledTooltip = ({ interactive = false, type = 'label', side, + documentElement, ...props }: { children: ReactNode @@ -28,6 +29,7 @@ const StyledTooltip = ({ interactive?: boolean type?: TooltipStoreProps['type'] side?: PopoverSide + documentElement?: HTMLElement } & Partial) => { const [forceOpen, setForceOpen] = useState() @@ -123,15 +125,15 @@ const StyledTooltip = ({ popoverElement.style.pointerEvents = 'none' } - const documentElement = document.querySelector('.main-ui-view') + const documentElementForPopover = documentElement || document.querySelector('.main-ui-view') - if (!popoverElement || !anchorElement || !documentElement || !open) { + if (!popoverElement || !anchorElement || !documentElementForPopover || !open) { return } const anchorRect = anchorElement.getBoundingClientRect() const popoverRect = popoverElement.getBoundingClientRect() - const documentRect = documentElement.getBoundingClientRect() + const documentRect = documentElementForPopover.getBoundingClientRect() const styles = getPositionedPopoverStyles({ align: 'center', @@ -151,7 +153,11 @@ const StyledTooltip = ({ Object.assign(popoverElement.style, styles) if (!props.portal) { - const adjustedStyles = getAdjustedStylesForNonPortalPopover(popoverElement, styles) + const adjustedStyles = getAdjustedStylesForNonPortalPopover( + popoverElement, + styles, + props.portalElement instanceof HTMLElement ? props.portalElement : undefined, + ) popoverElement.style.setProperty('--translate-x', adjustedStyles['--translate-x']) popoverElement.style.setProperty('--translate-y', adjustedStyles['--translate-y']) } diff --git a/packages/web/src/javascripts/Components/SuperEditor/BlocksEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/BlocksEditor.tsx index 44e9d9d5a..4001680c5 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/BlocksEditor.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/BlocksEditor.tsx @@ -28,6 +28,13 @@ import TableActionMenuPlugin from './Plugins/TableCellActionMenuPlugin' import ToolbarPlugin from './Plugins/ToolbarPlugin/ToolbarPlugin' import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery' import { CheckListPlugin } from './Plugins/List/CheckListPlugin' +import RemoteImagePlugin from './Plugins/RemoteImagePlugin/RemoteImagePlugin' +import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions' +import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context' +import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin' +import AutoLinkPlugin from './Plugins/AutoLinkPlugin/AutoLinkPlugin' +import DatetimePlugin from './Plugins/DateTimePlugin/DateTimePlugin' +import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin' type BlocksEditorProps = { onChange?: (value: string, preview: string) => void @@ -83,7 +90,10 @@ export const BlocksEditor: FunctionComponent = ({
@@ -116,6 +126,14 @@ export const BlocksEditor: FunctionComponent = ({ + + + + + + + + {!readonly && floatingAnchorElem && ( <> diff --git a/packages/web/src/javascripts/Components/SuperEditor/BlocksEditorComposer.tsx b/packages/web/src/javascripts/Components/SuperEditor/BlocksEditorComposer.tsx index ed0cec74a..eb3e6c6b0 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/BlocksEditorComposer.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/BlocksEditorComposer.tsx @@ -1,11 +1,11 @@ import { FunctionComponent } from 'react' -import { LexicalComposer } from '@lexical/react/LexicalComposer' +import { LexicalComposer, InitialEditorStateType } from '@lexical/react/LexicalComposer' import BlocksEditorTheme from './Lexical/Theme/Theme' import { BlockEditorNodes } from './Lexical/Nodes/AllNodes' import { Klass, LexicalNode } from 'lexical' type BlocksEditorComposerProps = { - initialValue: string | undefined + initialValue: InitialEditorStateType | undefined children: React.ReactNode nodes?: Array> readonly?: boolean @@ -24,7 +24,7 @@ export const BlocksEditorComposer: FunctionComponent theme: BlocksEditorTheme, editable: !readonly, onError: (error: Error) => console.error(error), - editorState: initialValue && initialValue.length > 0 ? initialValue : undefined, + editorState: initialValue, nodes: [...nodes, ...BlockEditorNodes], }} > diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.css b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.css index 1474a584f..37dabeff6 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.css +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.css @@ -11,7 +11,7 @@ display: flex; justify-content: center; align-items: center; - position: fixed; + position: absolute; flex-direction: column; top: 0px; bottom: 0px; diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.tsx b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.tsx index 6348f93a9..5b6951765 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/Modal.tsx @@ -6,9 +6,10 @@ * */ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import './Modal.css' -import { ReactNode, useEffect, useRef } from 'react' +import { ReactNode, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' function PortalImpl({ @@ -83,11 +84,22 @@ export default function Modal({ closeOnClickOutside?: boolean onClose: () => void title: string -}): JSX.Element { +}): ReactNode { + const [containerElement, setContainerElement] = useState() + const [editor] = useLexicalComposerContext() + + useEffect(() => { + setContainerElement(editor.getRootElement()?.parentElement ?? document.body) + }, [editor]) + + if (!containerElement) { + return null + } + return createPortal( {children} , - document.body, + containerElement, ) } diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerMenuItem.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerMenuItem.tsx index 51f002a3e..4b5bf6c69 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerMenuItem.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerMenuItem.tsx @@ -19,7 +19,7 @@ export function BlockPickerMenuItem({
  • mh / 2} + overrideZIndex={popoverZIndex} >
      {options.map((option, i: number) => ( diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx index 676b1f6bc..7e54cd303 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx @@ -113,8 +113,19 @@ const ToolbarButton = forwardRef( ) => { const [editor] = useLexicalComposerContext() + const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) + const parentElement = editor.getRootElement()?.parentElement ?? document.body + return ( - + { toolbarStore, ]) + const popoverDocumentElement = editor.getRootElement()?.parentElement ?? document.body + return ( <> {modal} @@ -746,6 +759,8 @@ const ToolbarPlugin = () => { className="py-1" disableMobileFullscreenTakeover disableFlip + portal={false} + documentElement={isMobile ? popoverDocumentElement : undefined} >
      Table of Contents
      @@ -800,6 +815,8 @@ const ToolbarPlugin = () => { disableMobileFullscreenTakeover disableFlip containerClassName="md:!min-w-60 md:!w-auto" + portal={false} + documentElement={isMobile ? popoverDocumentElement : undefined} > setIsTextFormatMenuOpen(false)}> { disableMobileFullscreenTakeover disableFlip containerClassName="md:!min-w-60 md:!w-auto" + portal={false} + documentElement={isMobile ? popoverDocumentElement : undefined} > setIsTextStyleMenuOpen(false)}> { disableMobileFullscreenTakeover disableFlip containerClassName="md:!min-w-60 md:!w-auto" + portal={false} + documentElement={isMobile ? popoverDocumentElement : undefined} > setIsAlignmentMenuOpen(false)}> { disableMobileFullscreenTakeover disableFlip containerClassName="md:!min-w-60 md:!w-auto" + portal={false} + documentElement={isMobile ? popoverDocumentElement : undefined} > setIsInsertMenuOpen(false)}> = ({ = ({ - - - - (changeEditorFunction.current = callback)} /> @@ -230,11 +215,7 @@ export const SuperEditor: FunctionComponent = ({ {readonly === undefined && } - - - - - + diff --git a/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx b/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx index 846f6fcf0..522e1a9a1 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx @@ -155,7 +155,11 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface { return content } - convertOtherFormatToSuperString(otherFormatString: string, fromFormat: 'txt' | 'md' | 'html' | 'json'): string { + convertOtherFormatToSuperString: SuperConverterServiceInterface['convertOtherFormatToSuperString'] = ( + otherFormatString, + fromFormat, + options, + ) => { if (otherFormatString.length === 0) { return otherFormatString } @@ -175,6 +179,10 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface { let didThrow = false if (fromFormat === 'html') { + const htmlOptions = options?.html || { + addLineBreaks: true, + } + this.importEditor.update( () => { try { @@ -203,7 +211,9 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface { nodesToInsert.push(node) } - nodesToInsert.push($createParagraphNode()) + if (htmlOptions.addLineBreaks) { + nodesToInsert.push($createParagraphNode()) + } }) $getRoot().selectEnd() $insertNodes(nodesToInsert.concat($createParagraphNode())) diff --git a/packages/web/src/javascripts/Controllers/FeaturesController.ts b/packages/web/src/javascripts/Controllers/FeaturesController.ts index 29d47fe7c..1ce26bac6 100644 --- a/packages/web/src/javascripts/Controllers/FeaturesController.ts +++ b/packages/web/src/javascripts/Controllers/FeaturesController.ts @@ -103,6 +103,10 @@ export class FeaturesController extends AbstractViewController implements Intern this.premiumAlertType = PremiumFeatureModalType.UpgradeSuccess } + showSuperDemoModal = () => { + this.premiumAlertType = PremiumFeatureModalType.SuperDemo + } + public closePremiumAlert() { this.premiumAlertType = undefined } diff --git a/packages/web/src/javascripts/Hooks/usePremiumModal.tsx b/packages/web/src/javascripts/Hooks/usePremiumModal.tsx index e7c7d999a..11eacc425 100644 --- a/packages/web/src/javascripts/Hooks/usePremiumModal.tsx +++ b/packages/web/src/javascripts/Hooks/usePremiumModal.tsx @@ -1,11 +1,14 @@ import { WebApplication } from '@/Application/WebApplication' import { observer } from 'mobx-react-lite' -import { FunctionComponent, createContext, useCallback, useContext, ReactNode } from 'react' +import { FunctionComponent, createContext, useCallback, useContext, ReactNode, useMemo } from 'react' import PremiumFeaturesModal from '@/Components/PremiumFeaturesModal/PremiumFeaturesModal' import ModalOverlay from '@/Components/Modal/ModalOverlay' +import { classNames } from '@standardnotes/snjs' +import { PremiumFeatureModalType } from '@/Components/PremiumFeaturesModal/PremiumFeatureModalType' type PremiumModalContextData = { activate: (featureName: string) => void + showSuperDemo: () => void } const PremiumModalContext = createContext(null) @@ -43,12 +46,23 @@ const PremiumModalProvider: FunctionComponent = observer(({ application, application.featuresController.closePremiumAlert() }, [application.featuresController]) + const showSuperDemo = useCallback(() => { + application.featuresController.showSuperDemoModal() + }, [application.featuresController]) + + const value: PremiumModalContextData = useMemo(() => ({ activate, showSuperDemo }), [activate, showSuperDemo]) + return ( <> = observer(({ application, type={application.featuresController.premiumAlertType!} /> - {children} + {children} ) })