Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Super note type to list of note types #2086

Merged
merged 1 commit into from Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/features/src/Domain/Feature/FeatureIdentifier.ts
Expand Up @@ -51,4 +51,4 @@ export enum FeatureIdentifier {
*/
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'

export const ExperimentalFeatures = [FeatureIdentifier.SuperEditor]
export const ExperimentalFeatures = []
11 changes: 10 additions & 1 deletion packages/features/src/Domain/Lists/ClientFeatures.ts
@@ -1,7 +1,7 @@
import { ClientFeatureDescription } from '../Feature/FeatureDescription'
import { PermissionName } from '../Permission/PermissionName'
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
import { SubscriptionName } from '@standardnotes/common'
import { RoleName, SubscriptionName } from '@standardnotes/common'

export function clientFeatures(): ClientFeatureDescription[] {
return [
Expand All @@ -12,6 +12,15 @@ export function clientFeatures(): ClientFeatureDescription[] {
permission_name: PermissionName.TagNesting,
description: 'Organize your tags into folders.',
},
{
name: 'Super Notes',
identifier: FeatureIdentifier.SuperEditor,
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
permission_name: PermissionName.SuperEditor,
description:
'Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
},
{
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
name: 'Smart Filters',
Expand Down
15 changes: 1 addition & 14 deletions packages/features/src/Domain/Lists/ExperimentalFeatures.ts
@@ -1,18 +1,5 @@
import { FeatureIdentifier } from './../Feature/FeatureIdentifier'
import { RoleName, SubscriptionName } from '@standardnotes/common'
import { FeatureDescription } from '../Feature/FeatureDescription'
import { PermissionName } from '../Permission/PermissionName'

export function experimentalFeatures(): FeatureDescription[] {
const superEditor: FeatureDescription = {
name: 'Super Notes',
identifier: FeatureIdentifier.SuperEditor,
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
permission_name: PermissionName.SuperEditor,
description:
'A new way to edit notes. Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
}

return [superEditor]
return []
}
10 changes: 9 additions & 1 deletion packages/snjs/lib/Services/Features/FeaturesService.spec.ts
Expand Up @@ -197,7 +197,7 @@ describe('featuresService', () => {

describe('loadUserRoles()', () => {
it('retrieves user roles and features from storage', async () => {
await createService().initializeFromDisk()
createService().initializeFromDisk()
expect(storageService.getValue).toHaveBeenCalledWith(StorageKey.UserRoles, undefined, [])
expect(storageService.getValue).toHaveBeenCalledWith(StorageKey.UserFeatures, undefined, [])
})
Expand Down Expand Up @@ -576,6 +576,14 @@ describe('featuresService', () => {
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NotInCurrentPlan)
})

it('availableInRoles-based features', async () => {
const featuresService = createService()

await featuresService.updateRolesAndFetchFeatures('123', [RoleName.ProUser])

expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
})

it('third party feature status', async () => {
const featuresService = createService()

Expand Down
14 changes: 6 additions & 8 deletions packages/snjs/lib/Services/Features/FeaturesService.ts
Expand Up @@ -506,13 +506,11 @@ export class SNFeaturesService
return FeatureStatus.Entitled
}

if (this.isExperimentalFeature(featureId)) {
const nativeFeature = FeaturesImports.FindNativeFeature(featureId)
if (nativeFeature) {
const hasRole = this.roles.some((role) => nativeFeature.availableInRoles?.includes(role))
if (hasRole) {
return FeatureStatus.Entitled
}
const nativeFeature = FeaturesImports.FindNativeFeature(featureId)
if (nativeFeature && nativeFeature.availableInRoles) {
const hasRole = this.roles.some((role) => nativeFeature.availableInRoles?.includes(role))
if (hasRole) {
return FeatureStatus.Entitled
}
}

Expand All @@ -525,7 +523,7 @@ export class SNFeaturesService
}
}

const isThirdParty = FeaturesImports.FindNativeFeature(featureId) == undefined
const isThirdParty = nativeFeature == undefined
if (isThirdParty) {
const component = this.itemManager
.getDisplayableComponents()
Expand Down
2 changes: 1 addition & 1 deletion packages/snjs/package.json
Expand Up @@ -28,7 +28,7 @@
"lint:eslint": "eslint --ext .ts lib/",
"lint:fix": "eslint --fix --ext .ts lib/",
"lint:tsc": "tsc --noEmit --emitDeclarationOnly false --project lib/tsconfig.json",
"test": "jest spec --coverage",
"test": "jest --coverage",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
},
"devDependencies": {
Expand Down
Expand Up @@ -28,6 +28,7 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
})
const [selectedEditorIcon, selectedEditorIconTint] = getIconAndTintForNoteType(
note?.noteType || selectedEditor?.package_info.note_type,
true,
)
const [isClickOutsideDisabled, setIsClickOutsideDisabled] = useState(false)

Expand Down
Expand Up @@ -12,6 +12,7 @@ import { reloadFont } from '../NoteView/FontFunctions'
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
import { SuperNoteImporter } from '../NoteView/SuperEditor/SuperNoteImporter'
import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem'
import { Pill } from '../Preferences/PreferencesComponents/Content'

type ChangeEditorMenuProps = {
application: WebApplication
Expand Down Expand Up @@ -188,17 +189,24 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
const onClickEditorItem = () => {
selectItem(item).catch(console.error)
}

return (
<MenuRadioButtonItem
key={item.name}
onClick={onClickEditorItem}
className={'flex-row-reverse py-2'}
className={'flex-row-reversed py-2'}
checked={item.isEntitled ? isSelected(item) : false}
info={item.description}
>
<div className="flex flex-grow items-center justify-between">
<div className={`flex items-center ${group.featured ? 'font-bold' : ''}`}>
{group.icon && <Icon type={group.icon} className={`mr-2 ${group.iconClassName}`} />}
{item.name}
{item.isLabs && (
<Pill className="py-0.5 px-1.5" style="success">
Labs
</Pill>
)}
</div>
{!item.isEntitled && (
<Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />
Expand Down
Expand Up @@ -22,6 +22,7 @@ import { classNames } from '@standardnotes/utils'
import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscriptionBanner'
import MenuRadioButtonItem from '@/Components/Menu/MenuRadioButtonItem'
import MenuSwitchButtonItem from '@/Components/Menu/MenuSwitchButtonItem'
import { Pill } from '@/Components/Preferences/PreferencesComponents/Content'

const DailyEntryModeEnabled = true

Expand Down Expand Up @@ -365,9 +366,9 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
<div className="flex flex-col pr-5">
<div className="flex flex-row items-center">
<div className="text-base font-semibold uppercase text-text lg:text-xs">Daily Notebook</div>
<div className="ml-2 rounded bg-warning px-1.5 py-[1px] text-[10px] font-bold text-warning-contrast">
<Pill className="py-0 px-1.5" style="success">
Labs
</div>
</Pill>
</div>
<div className="mt-1">Capture new notes daily with a calendar-based layout</div>
</div>
Expand Down
@@ -1,20 +1,60 @@
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { classNames } from '@standardnotes/snjs'
import { PlatformedKeyboardShortcut } from '@standardnotes/ui-services'
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef, ReactNode } from 'react'
import {
ComponentPropsWithoutRef,
ForwardedRef,
forwardRef,
MouseEventHandler,
ReactNode,
useCallback,
useState,
} from 'react'
import Icon from '../Icon/Icon'
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
import RadioIndicator from '../Radio/RadioIndicator'
import MenuListItem from './MenuListItem'

const Tooltip = ({ text }: { text: string }) => {
const [mobileVisible, setMobileVisible] = useState(false)
const onClickMobile: MouseEventHandler<HTMLDivElement> = useCallback(
(event) => {
event.preventDefault()
event.stopPropagation()
setMobileVisible(!mobileVisible)
},
[mobileVisible],
)

return (
<div className="relative">
<div className={classNames('peer flex h-5 w-5 items-center justify-center rounded-full')} onClick={onClickMobile}>
<Icon type={'help'} className="text-neutral" size="large" />
<span className="sr-only">Note sync status</span>
</div>
<div
className={classNames(
'hidden',
'absolute top-full right-0 w-60 translate-x-2 translate-y-1 select-none rounded border border-border shadow-main',
'bg-default py-1.5 px-3 text-left peer-hover:block peer-focus:block',
)}
>
{text}
</div>
</div>
)
}

type Props = {
checked: boolean
children: ReactNode
shortcut?: PlatformedKeyboardShortcut
info?: string
} & ComponentPropsWithoutRef<'button'>

const MenuRadioButtonItem = forwardRef(
(
{ checked, disabled, tabIndex, children, shortcut, className, ...props }: Props,
{ checked, disabled, tabIndex, children, shortcut, className, info, ...props }: Props,
ref: ForwardedRef<HTMLButtonElement>,
) => {
return (
Expand All @@ -37,6 +77,7 @@ const MenuRadioButtonItem = forwardRef(
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
<RadioIndicator disabled={disabled} checked={checked} className="flex-shrink-0" />
{children}
{info && <Tooltip text={info} />}
</button>
</MenuListItem>
)
Expand Down
Expand Up @@ -5,4 +5,6 @@ export type EditorMenuItem = {
component?: SNComponent
isEntitled: boolean
noteType: NoteType
isLabs?: boolean
description?: string
}
Expand Up @@ -24,8 +24,8 @@ const General: FunctionComponent<Props> = ({ viewControllerManager, application,
<Defaults application={application} />
<Tools application={application} />
<SmartViews application={application} featuresController={viewControllerManager.featuresController} />
<LabsPane application={application} />
<Moments application={application} />
<LabsPane application={application} />
<Advanced
application={application}
viewControllerManager={viewControllerManager}
Expand Down
Expand Up @@ -80,7 +80,7 @@ const Moments: FunctionComponent<Props> = ({ application }: Props) => {
<div className="flex items-center justify-between">
<div className="flex items-start">
<Title>Moments</Title>
<Pill style={'warning'}>Labs</Pill>
<Pill style={'success'}>Labs</Pill>
<Pill style={'info'}>Professional</Pill>
</div>
<Switch onChange={toggle} checked={momentsEnabled} />
Expand Down
Expand Up @@ -58,7 +58,7 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
}

const reloadDesktopAutoLockInterval = useCallback(async () => {
const interval = await application.getAutolockService()!.getAutoLockInterval()
const interval = await application.getAutolockService()?.getAutoLockInterval()
setSelectedAutoLockInterval(interval)
}, [application])

Expand Down Expand Up @@ -86,7 +86,7 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
return
}

await application.getAutolockService()!.setAutoLockInterval(interval)
await application.getAutolockService()?.setAutoLockInterval(interval)
reloadDesktopAutoLockInterval().catch(console.error)
}

Expand Down Expand Up @@ -184,6 +184,12 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
setPasscodeConfirmation(undefined)
}

const autolockService = application.getAutolockService()

if (!autolockService) {
return null
}

return (
<>
<PreferencesGroup>
Expand Down Expand Up @@ -248,25 +254,22 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
<Title>Autolock</Title>
<Text className="mb-3">The autolock timer begins when the window or tab loses focus.</Text>
<div className="flex flex-row items-center">
{application
.getAutolockService()!
.getAutoLockIntervalOptions()
.map((option) => {
return (
<a
key={option.value}
className={classNames(
'mr-3 cursor-pointer rounded',
option.value === selectedAutoLockInterval
? 'bg-info px-1.5 py-0.5 text-info-contrast'
: 'text-info',
)}
onClick={() => selectDesktopAutoLockInterval(option.value)}
>
{option.label}
</a>
)
})}
{autolockService.getAutoLockIntervalOptions().map((option) => {
return (
<a
key={option.value}
className={classNames(
'mr-3 cursor-pointer rounded',
option.value === selectedAutoLockInterval
? 'bg-info px-1.5 py-0.5 text-info-contrast'
: 'text-info',
)}
onClick={() => selectDesktopAutoLockInterval(option.value)}
>
{option.label}
</a>
)
})}
</div>
</PreferencesSegment>
</PreferencesGroup>
Expand Down
Expand Up @@ -31,7 +31,7 @@ const PremiumFeaturesModal: FunctionComponent<Props> = ({

return (
<AlertDialog leastDestructiveRef={ctaButtonRef} className="p-0">
<div tabIndex={-1} className="sn-component">
<div tabIndex={-1} className="sn-component bg-default">
<div tabIndex={0} className="max-w-89 rounded bg-default p-4 shadow-main">
{type === PremiumFeatureModalType.UpgradePrompt && (
<UpgradePrompt
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/javascripts/Constants/Constants.ts
Expand Up @@ -29,15 +29,17 @@ export const SYNC_TIMEOUT_NO_DEBOUNCE = 100
type EditorMetadata = {
name: string
icon: IconType
subtleIcon?: IconType
iconClassName: string
iconTintNumber: number
}

export const SuperEditorMetadata: EditorMetadata = {
name: 'Super',
icon: 'file-doc',
subtleIcon: 'format-align-left',
iconClassName: 'text-accessory-tint-4',
iconTintNumber: 4,
iconTintNumber: 1,
}

export const PlainEditorMetadata: EditorMetadata = {
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/javascripts/FeatureTrunk.ts
Expand Up @@ -4,7 +4,7 @@ export enum FeatureTrunkName {
Super,
}

export const FeatureTrunkStatus: Record<FeatureTrunkName, boolean> = {
const FeatureTrunkStatus: Record<FeatureTrunkName, boolean> = {
[FeatureTrunkName.Super]: isDev && true,
}

Expand Down