chore: show Super demo modal if user doesn't have subscription when switching editor to Super
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
export interface SuperConverterServiceInterface {
|
||||
isValidSuperString(superString: string): boolean
|
||||
convertSuperStringToOtherFormat: (superString: string, toFormat: 'txt' | 'md' | 'html' | 'json') => Promise<string>
|
||||
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[]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -154,6 +154,11 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
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<ChangeEditorMenuProps> = ({
|
||||
<>
|
||||
<Menu className="pb-1 pt-0.5" a11yLabel="Change note type menu">
|
||||
<MenuSection>
|
||||
<div className="flex items-center justify-between pr-4 py-3 md:pt-0 md:pb-1">
|
||||
<div className="flex items-center justify-between py-3 pr-4 md:pb-1 md:pt-0">
|
||||
<div className="px-3">
|
||||
<h2 className="text-base font-bold">Choose a note type</h2>
|
||||
{unableToFindEditor && (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ const PositionedPopoverContent = ({
|
||||
hideOnClickInModal = false,
|
||||
setAnimationElement,
|
||||
containerClassName,
|
||||
documentElement,
|
||||
}: PopoverContentProps) => {
|
||||
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(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({
|
||||
|
||||
@@ -50,6 +50,7 @@ type CommonPopoverProps = {
|
||||
offset?: number
|
||||
hideOnClickInModal?: boolean
|
||||
open: boolean
|
||||
documentElement?: HTMLElement
|
||||
}
|
||||
|
||||
export type PopoverContentProps = CommonPopoverProps & {
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum PremiumFeatureModalType {
|
||||
UpgradePrompt,
|
||||
UpgradeSuccess,
|
||||
SuperDemo,
|
||||
}
|
||||
|
||||
@@ -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<Props> = ({
|
||||
}) => {
|
||||
const ctaButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<Modal close={onClose} title="Upgrade" className="px-6 py-5" customHeader={<></>}>
|
||||
<div tabIndex={-1} className="sn-component">
|
||||
<div tabIndex={0}>
|
||||
{type === PremiumFeatureModalType.UpgradePrompt && (
|
||||
<UpgradePrompt
|
||||
featureName={featureName}
|
||||
ctaRef={ctaButtonRef}
|
||||
application={application}
|
||||
hasSubscription={hasSubscription}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)}
|
||||
const isShowingSuperDemo = type === PremiumFeatureModalType.SuperDemo
|
||||
|
||||
{type === PremiumFeatureModalType.UpgradeSuccess && <SuccessPrompt ctaRef={ctaButtonRef} onClose={onClose} />}
|
||||
</div>
|
||||
</div>
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
close={onClose}
|
||||
title={isShowingSuperDemo ? 'Try out Super' : 'Upgrade'}
|
||||
className={isShowingSuperDemo ? '' : 'px-6 py-5'}
|
||||
customHeader={isShowingSuperDemo ? undefined : <></>}
|
||||
actions={
|
||||
isShowingSuperDemo
|
||||
? [
|
||||
{
|
||||
label: 'Done',
|
||||
type: 'primary',
|
||||
onClick: onClose,
|
||||
hidden: !isMobileScreen,
|
||||
mobileSlot: 'right',
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{type === PremiumFeatureModalType.UpgradePrompt && (
|
||||
<UpgradePrompt
|
||||
featureName={featureName}
|
||||
ctaRef={ctaButtonRef}
|
||||
application={application}
|
||||
hasSubscription={hasSubscription}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)}
|
||||
{type === PremiumFeatureModalType.UpgradeSuccess && <SuccessPrompt ctaRef={ctaButtonRef} onClose={onClose} />}
|
||||
{type === PremiumFeatureModalType.SuperDemo && <SuperDemo hasSubscription={hasSubscription} onClose={onClose} />}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<HTMLButtonElement>(null)
|
||||
|
||||
const [demoContainer, setDemoContainer] = useState<HTMLDivElement | null>(null)
|
||||
const demoContainerRect = useAutoElementRect(demoContainer, {
|
||||
updateOnWindowResize: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col" ref={setDemoContainer}>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex-shrink-0 border-b border-border p-4',
|
||||
demoContainerRect && demoContainerRect.height < 500 ? 'hidden md:block' : '',
|
||||
)}
|
||||
>
|
||||
<UpgradePrompt
|
||||
featureName="Super notes"
|
||||
ctaRef={ctaRef}
|
||||
application={application}
|
||||
hasSubscription={hasSubscription}
|
||||
inline
|
||||
preferHorizontalLayout
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="relative flex h-full min-h-0 flex-col"
|
||||
style={
|
||||
{
|
||||
'--line-height': EditorLineHeightValues[lineHeight],
|
||||
'--font-size': responsiveFontSize,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<BlocksEditorComposer initialValue={SuperDemoInitialValue()}>
|
||||
<BlocksEditor className="blocks-editor h-full bg-default">
|
||||
<BlockPickerMenuPlugin popoverZIndex="z-modal" />
|
||||
</BlocksEditor>
|
||||
</BlocksEditorComposer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SuperDemo
|
||||
@@ -0,0 +1,164 @@
|
||||
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'
|
||||
|
||||
const InitialHTML = `<div>
|
||||
<h1>This is a demo of Super notes</h1>
|
||||
<p><br></p>
|
||||
<p>Super notes are our new <b>rich text</b> experience. With Super notes, you can create <b>rich</b>, <i>dynamic</i> text with powerful options.</p>
|
||||
<p><br></p>
|
||||
<h2><span>Images</span></h2>
|
||||
<p><br></p>
|
||||
<p>You can add images to your note by selecting the "Image from URL" option from the <code spellcheck="false"><span>/</span></code> menu or Insert menu in the toolbar.</p>
|
||||
<p><br></p>
|
||||
<p><img src="https://standardnotes.com/static/292c6ba50c69a3ae4f8b1883e7f505f6/1f7f6/vault-wide.jpg" /></p>
|
||||
<p><br></p>
|
||||
<h2><span>Lists</span></h2>
|
||||
<p><br></p>
|
||||
<ul>
|
||||
<li value="1"><span>Type </span><code spellcheck="false"><span>-</span></code><span> followed by a space in begin a
|
||||
list</span></li>
|
||||
<li value="2"><span>Type </span><code spellcheck="false"><span>1.</span></code><span> followed by a space in begin a numbered
|
||||
list</span></li>
|
||||
<li value="3"><span>Type </span><code spellcheck="false"><span>[]</span></code><span> followed by a space
|
||||
to begin a checklist </span></li>
|
||||
</ul>
|
||||
<p><br></p>
|
||||
<ul>
|
||||
<li value="1"><span>A list</span></li>
|
||||
<li value="2">
|
||||
<ul>
|
||||
<li value="1"><span>Indent the list</span></li>
|
||||
<li value="2">
|
||||
<ul>
|
||||
<li value="1"><span>And even more</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p><br></p>
|
||||
<ol>
|
||||
<li value="1"><span>A numbered list</span></li>
|
||||
<li value="2"><span>With multiple levels</span></li>
|
||||
<li value="3"><span>And even more</span></li>
|
||||
</ol>
|
||||
<p><br></p>
|
||||
<ul __lexicallisttype="check">
|
||||
<li role="checkbox" tabindex="-1" aria-checked="false" value="1">
|
||||
<span>Create</span>
|
||||
</li>
|
||||
<li role="checkbox" tabindex="-1" aria-checked="true" value="2">
|
||||
<span>a</span>
|
||||
</li>
|
||||
<li role="checkbox" tabindex="-1" aria-checked="true" value="3">
|
||||
<span>checklist</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p><br></p>
|
||||
<h2><span>Collapsible sections</span></h2>
|
||||
<p><br></p>
|
||||
<details open="">
|
||||
<summary><span>Collapsible section</span></summary>
|
||||
<div data-lexical-collapsible-content="true">
|
||||
<p><span>Collapsible sections can include all
|
||||
other types of content like</span></p>
|
||||
<p><br></p>
|
||||
<h2><span>Heading</span></h2>
|
||||
<p><br></p>
|
||||
<ul>
|
||||
<li value="1"><span>a list</span></li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li value="1"><span>numbered</span></li>
|
||||
</ol>
|
||||
<ul __lexicallisttype="check">
|
||||
<li role="checkbox" tabindex="-1" aria-checked="false" value="1"><span>check
|
||||
list</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p><br></p>
|
||||
<pre spellcheck="false" data-highlight-language="javascript"><span>A</span><span> code block</span></pre>
|
||||
<p><br></p>
|
||||
<p><span>You can even nest collapsible
|
||||
sections.</span></p>
|
||||
<p><br></p>
|
||||
<details open="">
|
||||
<summary><span>Nested collapsible section</span></summary>
|
||||
<div data-lexical-collapsible-content="true">
|
||||
<blockquote><span>Quote</span></blockquote>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
<p><br></p>
|
||||
<h2><span>Code blocks</span></h2>
|
||||
<p><br></p>
|
||||
<p><span>Type </span><code spellcheck="false"><span >\`\`\`</span></code><span> followed by a space to create a code
|
||||
block. You can choose the language when your
|
||||
cursor is within the code block.</span></p>
|
||||
<p><br></p>
|
||||
<pre spellcheck="false"
|
||||
data-highlight-language="js"><span >function</span><span> </span><span >main</span><span >(</span><span >)</span><span> </span><span >{</span><br><span> </span><span >const</span><span> variable </span><span >=</span><span> </span><span >"string"</span><span >;</span><br><span> </span><span >return</span><span> </span><span >TEST</span><span >;</span><br><span >}</span></pre>
|
||||
<p><br></p>
|
||||
<h2><span>Tables</span></h2>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<p><span>Header</span></p>
|
||||
</th>
|
||||
<th>
|
||||
<p><span>Column 1</span></p>
|
||||
</th>
|
||||
<th>
|
||||
<p><span>Column 2</span></p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p><span>Row 1</span></p>
|
||||
</th>
|
||||
<td>
|
||||
<p><span>Row 1 x Column 1</span></p>
|
||||
</td>
|
||||
<td>
|
||||
<p><span>Row 1 x Column 2</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p><span>Row 2</span></p>
|
||||
</th>
|
||||
<td>
|
||||
<p><span>Row 2 x Column 1</span></p>
|
||||
</td>
|
||||
<td>
|
||||
<p><span>Row 2 x Column 2</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><br></p>
|
||||
<h2><span>Passwords</span></h2>
|
||||
<p><span>You can generate a secure password using
|
||||
the "Generate password" command using the </span><code spellcheck="false"><span >/</span></code><span>
|
||||
menu.</span></p>
|
||||
<p><br></p>
|
||||
<ul>
|
||||
<li value="1"><span>}:hcMrIFgaijpkyz</span></li>
|
||||
<li value="2"><span>*raF/qi$m?y?iiBS</span></li>
|
||||
<li value="3"><span>YuVmWf(gOD&=vjbB</span></li>
|
||||
</ul>
|
||||
</div>`
|
||||
|
||||
export function SuperDemoInitialValue() {
|
||||
return new HeadlessSuperConverter().convertOtherFormatToSuperString(InitialHTML, 'html', {
|
||||
html: {
|
||||
addLineBreaks: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<div>
|
||||
<div className={preferHorizontalLayout ? 'flex flex-wrap items-center gap-4 md:flex-nowrap' : ''}>
|
||||
{!inline && (
|
||||
<div className="flex justify-end p-1">
|
||||
{!inline && (
|
||||
<button
|
||||
className="flex cursor-pointer border-0 bg-transparent p-0"
|
||||
onClick={onClose}
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<Icon className="text-neutral" type="close" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="flex cursor-pointer border-0 bg-transparent p-0"
|
||||
onClick={onClose}
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<Icon className="text-neutral" type="close" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center justify-center rounded-[50%] bg-contrast',
|
||||
preferHorizontalLayout ? 'h-12 w-12 flex-shrink-0' : 'mx-auto mb-5 h-24 w-24',
|
||||
)}
|
||||
aria-hidden={true}
|
||||
>
|
||||
<Icon
|
||||
className={classNames(preferHorizontalLayout ? 'h-8 w-8' : 'h-12 w-12', PremiumFeatureIconClass)}
|
||||
size={'custom'}
|
||||
type={PremiumFeatureIconName}
|
||||
/>
|
||||
</div>
|
||||
<div className={preferHorizontalLayout ? '' : 'mb-2'}>
|
||||
<div className={classNames('mb-1 text-lg font-bold', preferHorizontalLayout ? 'text-left' : 'text-center')}>
|
||||
Enable Advanced Features
|
||||
</div>
|
||||
<div
|
||||
className="mx-auto mb-5 flex h-24 w-24 items-center justify-center rounded-[50%] bg-contrast"
|
||||
aria-hidden={true}
|
||||
className={classNames('text-sm text-passive-1', preferHorizontalLayout ? 'text-left' : 'px-4.5 text-center')}
|
||||
>
|
||||
<Icon className={`h-12 w-12 ${PremiumFeatureIconClass}`} size={'custom'} type={PremiumFeatureIconName} />
|
||||
{featureName && (
|
||||
<span>
|
||||
To take advantage of <span className="font-semibold">{featureName}</span> and other advanced features,
|
||||
upgrade your current plan.
|
||||
</span>
|
||||
)}
|
||||
{!featureName && (
|
||||
<span>
|
||||
To take advantage of all the advanced features Standard Notes has to offer, upgrade your current plan.
|
||||
</span>
|
||||
)}
|
||||
{application.isNativeIOS() && (
|
||||
<div className="mt-2">
|
||||
<div className="mb-2 font-bold">The Professional Plan costs $119.99/year and includes benefits like</div>
|
||||
<ul className="list-inside list-[circle]">
|
||||
<li>100GB encrypted file storage</li>
|
||||
<li>
|
||||
Access to all note types, including Super, markdown, rich text, authenticator, tasks, and spreadsheets
|
||||
</li>
|
||||
<li>Access to Daily Notebooks and Moments journals</li>
|
||||
<li>Note history going back indefinitely</li>
|
||||
<li>Nested folders for your tags</li>
|
||||
<li>Premium support</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-1 text-center text-lg font-bold">Enable Advanced Features</div>
|
||||
</div>
|
||||
<div className="mb-2 px-4.5 text-center text-sm text-passive-1">
|
||||
{featureName && (
|
||||
<span>
|
||||
To take advantage of <span className="font-semibold">{featureName}</span> and other advanced features,
|
||||
upgrade your current plan.
|
||||
</span>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className={classNames(
|
||||
'no-border cursor-pointer rounded bg-info py-2 font-bold text-info-contrast hover:brightness-125 focus:brightness-125',
|
||||
preferHorizontalLayout ? 'w-full px-4 md:ml-auto md:w-auto' : 'my-2 w-full',
|
||||
)}
|
||||
{!featureName && (
|
||||
<span>
|
||||
To take advantage of all the advanced features Standard Notes has to offer, upgrade your current plan.
|
||||
</span>
|
||||
)}
|
||||
{application.isNativeIOS() && (
|
||||
<div className="mt-2">
|
||||
<div className="mb-2 font-bold">The Professional Plan costs $119.99/year and includes benefits like</div>
|
||||
<ul className="list-inside list-[circle]">
|
||||
<li>100GB encrypted file storage</li>
|
||||
<li>
|
||||
Access to all note types, including Super, markdown, rich text, authenticator, tasks, and spreadsheets
|
||||
</li>
|
||||
<li>Access to Daily Notebooks and Moments journals</li>
|
||||
<li>Note history going back indefinitely</li>
|
||||
<li>Nested folders for your tags</li>
|
||||
<li>Premium support</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="no-border w-full cursor-pointer rounded bg-info py-2 font-bold text-info-contrast hover:brightness-125 focus:brightness-125"
|
||||
ref={ctaRef}
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
ref={ctaRef}
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<TooltipOptions>) => {
|
||||
const [forceOpen, setForceOpen] = useState<boolean | undefined>()
|
||||
|
||||
@@ -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'])
|
||||
}
|
||||
|
||||
@@ -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<BlocksEditorProps> = ({
|
||||
<div className="editor z-0 overflow-hidden" ref={onRef}>
|
||||
<ContentEditable
|
||||
id={SuperEditorContentId}
|
||||
className={classNames('ContentEditable__root overflow-y-auto', className)}
|
||||
className={classNames(
|
||||
'ContentEditable__root relative overflow-y-auto p-4 text-[length:--font-size] leading-[--line-height] focus:shadow-none focus:outline-none',
|
||||
className,
|
||||
)}
|
||||
spellCheck={spellcheck}
|
||||
/>
|
||||
<div className="search-highlight-container pointer-events-none absolute left-0 top-0 h-full w-full" />
|
||||
@@ -116,6 +126,14 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<CollapsiblePlugin />
|
||||
<TabIndentationPlugin />
|
||||
<RemoveBrokenTablesPlugin />
|
||||
<RemoteImagePlugin />
|
||||
<CodeOptionsPlugin />
|
||||
<SuperSearchContextProvider>
|
||||
<SearchPlugin />
|
||||
</SuperSearchContextProvider>
|
||||
<DatetimePlugin />
|
||||
<PasswordPlugin />
|
||||
<AutoLinkPlugin />
|
||||
{!readonly && floatingAnchorElem && (
|
||||
<>
|
||||
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
|
||||
|
||||
@@ -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<Klass<LexicalNode>>
|
||||
readonly?: boolean
|
||||
@@ -24,7 +24,7 @@ export const BlocksEditorComposer: FunctionComponent<BlocksEditorComposerProps>
|
||||
theme: BlocksEditorTheme,
|
||||
editable: !readonly,
|
||||
onError: (error: Error) => console.error(error),
|
||||
editorState: initialValue && initialValue.length > 0 ? initialValue : undefined,
|
||||
editorState: initialValue,
|
||||
nodes: [...nodes, ...BlockEditorNodes],
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
|
||||
@@ -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<HTMLElement | undefined>()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
setContainerElement(editor.getRootElement()?.parentElement ?? document.body)
|
||||
}, [editor])
|
||||
|
||||
if (!containerElement) {
|
||||
return null
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<PortalImpl onClose={onClose} title={title} closeOnClickOutside={closeOnClickOutside}>
|
||||
{children}
|
||||
</PortalImpl>,
|
||||
document.body,
|
||||
containerElement,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function BlockPickerMenuItem({
|
||||
<li
|
||||
key={option.key}
|
||||
tabIndex={-1}
|
||||
className={`border-bottom gap-3 border-[0.5px] border-border ${PopoverItemClassNames} ${
|
||||
className={`gap-3 border-b-[0.5px] border-border ${PopoverItemClassNames} ${
|
||||
isSelected ? PopoverItemSelectedClassNames : ''
|
||||
}`}
|
||||
ref={option.setRefElement}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { GetDividerBlockOption } from '../Blocks/Divider'
|
||||
import { GetCollapsibleBlockOption } from '../Blocks/Collapsible'
|
||||
import { GetEmbedsBlockOptions } from '../Blocks/Embeds'
|
||||
|
||||
export default function BlockPickerMenuPlugin(): JSX.Element {
|
||||
export default function BlockPickerMenuPlugin({ popoverZIndex }: { popoverZIndex?: string }): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const application = useApplication()
|
||||
const [modal, showModal] = useModal()
|
||||
@@ -130,6 +130,7 @@ export default function BlockPickerMenuPlugin(): JSX.Element {
|
||||
disableMobileFullscreenTakeover={true}
|
||||
side={isMobileScreen() ? 'top' : 'bottom'}
|
||||
maxHeight={(mh) => mh / 2}
|
||||
overrideZIndex={popoverZIndex}
|
||||
>
|
||||
<ul>
|
||||
{options.map((option, i: number) => (
|
||||
|
||||
@@ -113,8 +113,19 @@ const ToolbarButton = forwardRef(
|
||||
) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const parentElement = editor.getRootElement()?.parentElement ?? document.body
|
||||
|
||||
return (
|
||||
<StyledTooltip showOnMobile showOnHover label={name} side="top">
|
||||
<StyledTooltip
|
||||
showOnMobile
|
||||
showOnHover
|
||||
label={name}
|
||||
side="top"
|
||||
portal={false}
|
||||
portalElement={isMobile ? parentElement : undefined}
|
||||
documentElement={parentElement}
|
||||
>
|
||||
<ToolbarItem
|
||||
className={classNames(
|
||||
'flex select-none items-center justify-center rounded p-0.5 focus:shadow-none focus:outline-none enabled:hover:bg-default enabled:focus-visible:bg-default disabled:opacity-50 md:border md:border-transparent enabled:hover:md:translucent-ui:border-[--popover-border-color]',
|
||||
@@ -572,6 +583,8 @@ const ToolbarPlugin = () => {
|
||||
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}
|
||||
>
|
||||
<div className="mb-1.5 mt-1 px-3 text-sm font-semibold uppercase text-text">Table of Contents</div>
|
||||
<LexicalTableOfContents>
|
||||
@@ -800,6 +815,8 @@ const ToolbarPlugin = () => {
|
||||
disableMobileFullscreenTakeover
|
||||
disableFlip
|
||||
containerClassName="md:!min-w-60 md:!w-auto"
|
||||
portal={false}
|
||||
documentElement={isMobile ? popoverDocumentElement : undefined}
|
||||
>
|
||||
<Menu a11yLabel="Text formatting options" className="!px-0" onClick={() => setIsTextFormatMenuOpen(false)}>
|
||||
<ToolbarMenuItem
|
||||
@@ -839,6 +856,8 @@ const ToolbarPlugin = () => {
|
||||
disableMobileFullscreenTakeover
|
||||
disableFlip
|
||||
containerClassName="md:!min-w-60 md:!w-auto"
|
||||
portal={false}
|
||||
documentElement={isMobile ? popoverDocumentElement : undefined}
|
||||
>
|
||||
<Menu a11yLabel="Text style" className="!px-0" onClick={() => setIsTextStyleMenuOpen(false)}>
|
||||
<ToolbarMenuItem
|
||||
@@ -910,6 +929,8 @@ const ToolbarPlugin = () => {
|
||||
disableMobileFullscreenTakeover
|
||||
disableFlip
|
||||
containerClassName="md:!min-w-60 md:!w-auto"
|
||||
portal={false}
|
||||
documentElement={isMobile ? popoverDocumentElement : undefined}
|
||||
>
|
||||
<Menu a11yLabel="Alignment" className="!px-0" onClick={() => setIsAlignmentMenuOpen(false)}>
|
||||
<ToolbarMenuItem
|
||||
@@ -949,6 +970,8 @@ const ToolbarPlugin = () => {
|
||||
disableMobileFullscreenTakeover
|
||||
disableFlip
|
||||
containerClassName="md:!min-w-60 md:!w-auto"
|
||||
portal={false}
|
||||
documentElement={isMobile ? popoverDocumentElement : undefined}
|
||||
>
|
||||
<Menu a11yLabel="Insert" className="!px-0" onClick={() => setIsInsertMenuOpen(false)}>
|
||||
<ToolbarMenuItem
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import {
|
||||
classNames,
|
||||
isPayloadSourceRetrieved,
|
||||
PrefKey,
|
||||
NativeFeatureIdentifier,
|
||||
@@ -14,7 +13,6 @@ import { BlocksEditorComposer } from './BlocksEditorComposer'
|
||||
import { ItemSelectionPlugin } from './Plugins/ItemSelectionPlugin/ItemSelectionPlugin'
|
||||
import { FileNode } from './Plugins/EncryptedFilePlugin/Nodes/FileNode'
|
||||
import FilePlugin from './Plugins/EncryptedFilePlugin/FilePlugin'
|
||||
import BlockPickerMenuPlugin from './Plugins/BlockPickerPlugin/BlockPickerPlugin'
|
||||
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import LinkingControllerProvider from '../../Controllers/LinkingControllerProvider'
|
||||
@@ -23,28 +21,22 @@ import ItemBubblePlugin from './Plugins/ItemBubblePlugin/ItemBubblePlugin'
|
||||
import { NodeObserverPlugin } from './Plugins/NodeObserverPlugin/NodeObserverPlugin'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import FilesControllerProvider from '@/Controllers/FilesControllerProvider'
|
||||
import DatetimePlugin from './Plugins/DateTimePlugin/DateTimePlugin'
|
||||
import AutoLinkPlugin from './Plugins/AutoLinkPlugin/AutoLinkPlugin'
|
||||
import { NoteViewController } from '../NoteView/Controller/NoteViewController'
|
||||
import {
|
||||
ChangeContentCallbackPlugin,
|
||||
ChangeEditorFunction,
|
||||
} from './Plugins/ChangeContentCallback/ChangeContentCallback'
|
||||
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
|
||||
import { useCommandService } from '@/Components/CommandProvider'
|
||||
import { SUPER_SHOW_MARKDOWN_PREVIEW } from '@standardnotes/ui-services'
|
||||
import { SuperNoteMarkdownPreview } from './SuperNoteMarkdownPreview'
|
||||
import GetMarkdownPlugin, { GetMarkdownPluginInterface } from './Plugins/GetMarkdownPlugin/GetMarkdownPlugin'
|
||||
import { useResponsiveEditorFontSize } from '@/Utils/getPlaintextFontSize'
|
||||
import ReadonlyPlugin from './Plugins/ReadonlyPlugin/ReadonlyPlugin'
|
||||
import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context'
|
||||
import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin'
|
||||
import ModalOverlay from '@/Components/Modal/ModalOverlay'
|
||||
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
||||
import RemoteImagePlugin from './Plugins/RemoteImagePlugin/RemoteImagePlugin'
|
||||
import NotEntitledBanner from '../ComponentView/NotEntitledBanner'
|
||||
import AutoFocusPlugin from './Plugins/AutoFocusPlugin'
|
||||
import usePreference from '@/Hooks/usePreference'
|
||||
import BlockPickerMenuPlugin from './Plugins/BlockPickerPlugin/BlockPickerPlugin'
|
||||
|
||||
export const SuperNotePreviewCharLimit = 160
|
||||
|
||||
@@ -207,10 +199,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<BlocksEditorComposer readonly={note.current.locked || readonly} initialValue={note.current.text}>
|
||||
<BlocksEditor
|
||||
onChange={handleChange}
|
||||
className={classNames(
|
||||
'blocks-editor relative h-full resize-none px-4 py-4 text-[length:--font-size] focus:shadow-none focus:outline-none',
|
||||
lineHeight && 'leading-[--line-height]',
|
||||
)}
|
||||
className="blocks-editor h-full resize-none"
|
||||
previewLength={SuperNotePreviewCharLimit}
|
||||
spellcheck={spellcheck}
|
||||
readonly={note.current.locked || readonly}
|
||||
@@ -218,11 +207,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<ItemSelectionPlugin currentNote={note.current} />
|
||||
<FilePlugin currentNote={note.current} />
|
||||
<ItemBubblePlugin />
|
||||
<BlockPickerMenuPlugin />
|
||||
<GetMarkdownPlugin ref={getMarkdownPlugin} />
|
||||
<DatetimePlugin />
|
||||
<PasswordPlugin />
|
||||
<AutoLinkPlugin />
|
||||
<ChangeContentCallbackPlugin
|
||||
providerCallback={(callback) => (changeEditorFunction.current = callback)}
|
||||
/>
|
||||
@@ -230,11 +215,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<NodeObserverPlugin nodeType={FileNode} onRemove={handleBubbleRemove} />
|
||||
{readonly === undefined && <ReadonlyPlugin note={note.current} />}
|
||||
<AutoFocusPlugin isEnabled={controller.isTemplateNote} />
|
||||
<SuperSearchContextProvider>
|
||||
<SearchPlugin />
|
||||
</SuperSearchContextProvider>
|
||||
<CodeOptionsPlugin />
|
||||
<RemoteImagePlugin />
|
||||
<BlockPickerMenuPlugin />
|
||||
</BlocksEditor>
|
||||
</BlocksEditorComposer>
|
||||
</FilesControllerProvider>
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<PremiumModalContextData | null>(null)
|
||||
@@ -43,12 +46,23 @@ const PremiumModalProvider: FunctionComponent<Props> = observer(({ application,
|
||||
application.featuresController.closePremiumAlert()
|
||||
}, [application.featuresController])
|
||||
|
||||
const showSuperDemo = useCallback(() => {
|
||||
application.featuresController.showSuperDemoModal()
|
||||
}, [application.featuresController])
|
||||
|
||||
const value: PremiumModalContextData = useMemo(() => ({ activate, showSuperDemo }), [activate, showSuperDemo])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalOverlay
|
||||
isOpen={application.featuresController.premiumAlertType != undefined}
|
||||
close={close}
|
||||
className="w-full max-w-[90vw] !h-auto md:max-w-89"
|
||||
className={classNames(
|
||||
'w-full',
|
||||
application.featuresController.premiumAlertType === PremiumFeatureModalType.SuperDemo
|
||||
? 'md:!h-full md:w-full md:max-w-[70vw]'
|
||||
: '!h-auto max-w-[90vw] md:max-w-89',
|
||||
)}
|
||||
backdropClassName="!opacity-50"
|
||||
>
|
||||
<PremiumFeaturesModal
|
||||
@@ -59,7 +73,7 @@ const PremiumModalProvider: FunctionComponent<Props> = observer(({ application,
|
||||
type={application.featuresController.premiumAlertType!}
|
||||
/>
|
||||
</ModalOverlay>
|
||||
<PremiumModalProvider_ value={{ activate }}>{children}</PremiumModalProvider_>
|
||||
<PremiumModalProvider_ value={value}>{children}</PremiumModalProvider_>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user