fix: Fixes issue where lock screen would not use previously active theme (#2372)
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
import { FeatureIdentifier } from './../Feature/FeatureIdentifier'
|
|
||||||
|
|
||||||
type ThirdPartyIdentifier = string
|
|
||||||
|
|
||||||
export type EditorIdentifier = FeatureIdentifier | ThirdPartyIdentifier
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { noteTypeForEditorIdentifier, NoteType } from './NoteType'
|
import { noteTypeForEditorIdentifier, NoteType } from './NoteType'
|
||||||
|
|
||||||
describe('note type', () => {
|
describe('note type', () => {
|
||||||
it('should return the correct note type for editor identifier', () => {
|
it('should return the correct note type for editor identifier', () => {
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.PlainEditor)).toEqual(NoteType.Plain)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.PlainEditor)).toEqual(NoteType.Plain)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.SuperEditor)).toEqual(NoteType.Super)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.SuperEditor)).toEqual(NoteType.Super)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.MarkdownProEditor)).toEqual(NoteType.Markdown)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.MarkdownProEditor)).toEqual(NoteType.Markdown)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.PlusEditor)).toEqual(NoteType.RichText)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.PlusEditor)).toEqual(NoteType.RichText)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.CodeEditor)).toEqual(NoteType.Code)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.CodeEditor)).toEqual(NoteType.Code)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.SheetsEditor)).toEqual(NoteType.Spreadsheet)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.SheetsEditor)).toEqual(NoteType.Spreadsheet)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.TaskEditor)).toEqual(NoteType.Task)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.TaskEditor)).toEqual(NoteType.Task)
|
||||||
expect(noteTypeForEditorIdentifier(FeatureIdentifier.TokenVaultEditor)).toEqual(NoteType.Authentication)
|
expect(noteTypeForEditorIdentifier(NativeFeatureIdentifier.TYPES.TokenVaultEditor)).toEqual(NoteType.Authentication)
|
||||||
expect(noteTypeForEditorIdentifier('org.third.party')).toEqual(NoteType.Unknown)
|
expect(noteTypeForEditorIdentifier('org.third.party')).toEqual(NoteType.Unknown)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { EditorFeatureDescription } from '../Feature/EditorFeatureDescription'
|
import { EditorFeatureDescription } from '../Feature/EditorFeatureDescription'
|
||||||
import { FindNativeFeature } from '../Feature/Features'
|
import { FindNativeFeature } from '../Feature/Features'
|
||||||
import { IframeComponentFeatureDescription } from '../Feature/IframeComponentFeatureDescription'
|
import { IframeComponentFeatureDescription } from '../Feature/IframeComponentFeatureDescription'
|
||||||
import { FeatureIdentifier } from './../Feature/FeatureIdentifier'
|
|
||||||
import { EditorIdentifier } from './EditorIdentifier'
|
|
||||||
|
|
||||||
export enum NoteType {
|
export enum NoteType {
|
||||||
Authentication = 'authentication',
|
Authentication = 'authentication',
|
||||||
@@ -16,10 +14,8 @@ export enum NoteType {
|
|||||||
Unknown = 'unknown',
|
Unknown = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function noteTypeForEditorIdentifier(identifier: EditorIdentifier): NoteType {
|
export function noteTypeForEditorIdentifier(identifier: string): NoteType {
|
||||||
const feature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(
|
const feature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(identifier)
|
||||||
identifier as FeatureIdentifier,
|
|
||||||
)
|
|
||||||
if (feature && feature.note_type) {
|
if (feature && feature.note_type) {
|
||||||
return feature.note_type
|
return feature.note_type
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from './FeatureIdentifier'
|
|
||||||
import { ComponentFlag } from '../Component/ComponentFlag'
|
import { ComponentFlag } from '../Component/ComponentFlag'
|
||||||
import { RoleFields } from './RoleFields'
|
import { RoleFields } from './RoleFields'
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ export type BaseFeatureDescription = RoleFields & {
|
|||||||
clientControlled?: boolean
|
clientControlled?: boolean
|
||||||
|
|
||||||
flags?: ComponentFlag[]
|
flags?: ComponentFlag[]
|
||||||
identifier: FeatureIdentifier
|
identifier: string
|
||||||
marketing_url?: string
|
marketing_url?: string
|
||||||
name: string
|
name: string
|
||||||
no_expire?: boolean
|
no_expire?: boolean
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from './FeatureIdentifier'
|
|
||||||
import { RoleFields } from './RoleFields'
|
import { RoleFields } from './RoleFields'
|
||||||
|
|
||||||
export type ClientFeatureDescription = RoleFields & {
|
export type ClientFeatureDescription = RoleFields & {
|
||||||
identifier: FeatureIdentifier
|
identifier: string
|
||||||
permission_name: PermissionName
|
permission_name: PermissionName
|
||||||
description: string
|
description: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
export enum FeatureIdentifier {
|
|
||||||
DailyEmailBackup = 'org.standardnotes.daily-email-backup',
|
|
||||||
Files = 'org.standardnotes.files',
|
|
||||||
FilesLowStorageTier = 'org.standardnotes.files-low-storage-tier',
|
|
||||||
FilesMaximumStorageTier = 'org.standardnotes.files-max-storage-tier',
|
|
||||||
ListedCustomDomain = 'org.standardnotes.listed-custom-domain',
|
|
||||||
NoteHistory30Days = 'org.standardnotes.note-history-30',
|
|
||||||
NoteHistory365Days = 'org.standardnotes.note-history-365',
|
|
||||||
NoteHistoryUnlimited = 'org.standardnotes.note-history-unlimited',
|
|
||||||
SignInAlerts = 'com.standardnotes.sign-in-alerts',
|
|
||||||
SmartFilters = 'org.standardnotes.smart-filters',
|
|
||||||
TagNesting = 'org.standardnotes.tag-nesting',
|
|
||||||
TwoFactorAuth = 'org.standardnotes.two-factor-auth',
|
|
||||||
UniversalSecondFactor = 'org.standardnotes.universal-second-factor',
|
|
||||||
SubscriptionSharing = 'org.standardnotes.subscription-sharing',
|
|
||||||
|
|
||||||
AutobiographyTheme = 'org.standardnotes.theme-autobiography',
|
|
||||||
DynamicTheme = 'org.standardnotes.theme-dynamic',
|
|
||||||
DarkTheme = 'org.standardnotes.theme-focus',
|
|
||||||
FuturaTheme = 'org.standardnotes.theme-futura',
|
|
||||||
MidnightTheme = 'org.standardnotes.theme-midnight',
|
|
||||||
SolarizedDarkTheme = 'org.standardnotes.theme-solarized-dark',
|
|
||||||
TitaniumTheme = 'org.standardnotes.theme-titanium',
|
|
||||||
|
|
||||||
PlainEditor = 'com.standardnotes.plain-text',
|
|
||||||
SuperEditor = 'com.standardnotes.super-editor',
|
|
||||||
|
|
||||||
CodeEditor = 'org.standardnotes.code-editor',
|
|
||||||
MarkdownProEditor = 'org.standardnotes.advanced-markdown-editor',
|
|
||||||
PlusEditor = 'org.standardnotes.plus-editor',
|
|
||||||
SheetsEditor = 'org.standardnotes.standard-sheets',
|
|
||||||
TaskEditor = 'org.standardnotes.simple-task-editor',
|
|
||||||
TokenVaultEditor = 'org.standardnotes.token-vault',
|
|
||||||
|
|
||||||
Extension = 'org.standardnotes.extension',
|
|
||||||
|
|
||||||
DeprecatedMarkdownVisualEditor = 'org.standardnotes.markdown-visual-editor',
|
|
||||||
DeprecatedBoldEditor = 'org.standardnotes.bold-editor',
|
|
||||||
DeprecatedMarkdownBasicEditor = 'org.standardnotes.simple-markdown-editor',
|
|
||||||
DeprecatedMarkdownMathEditor = 'org.standardnotes.fancy-markdown-editor',
|
|
||||||
DeprecatedMarkdownMinimistEditor = 'org.standardnotes.minimal-markdown-editor',
|
|
||||||
DeprecatedFoldersComponent = 'org.standardnotes.folders',
|
|
||||||
DeprecatedFileSafe = 'org.standardnotes.file-safe',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier for standalone filesafe instance offered as legacy installable via extensions-server
|
|
||||||
*/
|
|
||||||
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
|
|
||||||
|
|
||||||
export const ExperimentalFeatures = []
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AnyFeatureDescription } from './AnyFeatureDescription'
|
import { AnyFeatureDescription } from './AnyFeatureDescription'
|
||||||
import { ThemeFeatureDescription } from './ThemeFeatureDescription'
|
import { ThemeFeatureDescription } from './ThemeFeatureDescription'
|
||||||
import { EditorFeatureDescription } from './EditorFeatureDescription'
|
import { EditorFeatureDescription } from './EditorFeatureDescription'
|
||||||
import { FeatureIdentifier } from './FeatureIdentifier'
|
import { NativeFeatureIdentifier } from './NativeFeatureIdentifier'
|
||||||
import { serverFeatures } from '../Lists/ServerFeatures'
|
import { serverFeatures } from '../Lists/ServerFeatures'
|
||||||
import { clientFeatures } from '../Lists/ClientFeatures'
|
import { clientFeatures } from '../Lists/ClientFeatures'
|
||||||
import { GetDeprecatedFeatures } from '../Lists/DeprecatedFeatures'
|
import { GetDeprecatedFeatures } from '../Lists/DeprecatedFeatures'
|
||||||
@@ -23,11 +23,11 @@ export function GetFeatures(): AnyFeatureDescription[] {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FindNativeFeature<T extends AnyFeatureDescription>(identifier: FeatureIdentifier): T | undefined {
|
export function FindNativeFeature<T extends AnyFeatureDescription>(identifier: string): T | undefined {
|
||||||
return GetFeatures().find((f) => f.identifier === identifier) as T
|
return GetFeatures().find((f) => f.identifier === identifier) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FindNativeTheme(identifier: FeatureIdentifier): ThemeFeatureDescription | undefined {
|
export function FindNativeTheme(identifier: string): ThemeFeatureDescription | undefined {
|
||||||
return themes().find((t) => t.identifier === identifier)
|
return themes().find((t) => t.identifier === identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,11 +40,11 @@ export function GetIframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GetSuperNoteFeature(): EditorFeatureDescription {
|
export function GetSuperNoteFeature(): EditorFeatureDescription {
|
||||||
return FindNativeFeature(FeatureIdentifier.SuperEditor) as EditorFeatureDescription
|
return FindNativeFeature(NativeFeatureIdentifier.TYPES.SuperEditor) as EditorFeatureDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetPlainNoteFeature(): EditorFeatureDescription {
|
export function GetPlainNoteFeature(): EditorFeatureDescription {
|
||||||
return FindNativeFeature(FeatureIdentifier.PlainEditor) as EditorFeatureDescription
|
return FindNativeFeature(NativeFeatureIdentifier.TYPES.PlainEditor) as EditorFeatureDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetNativeThemes(): ThemeFeatureDescription[] {
|
export function GetNativeThemes(): ThemeFeatureDescription[] {
|
||||||
@@ -52,5 +52,5 @@ export function GetNativeThemes(): ThemeFeatureDescription[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GetDarkThemeFeature(): ThemeFeatureDescription {
|
export function GetDarkThemeFeature(): ThemeFeatureDescription {
|
||||||
return themes().find((t) => t.identifier === FeatureIdentifier.DarkTheme) as ThemeFeatureDescription
|
return themes().find((t) => t.identifier === NativeFeatureIdentifier.TYPES.DarkTheme) as ThemeFeatureDescription
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
export interface NativeFeatureIdentifierProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NativeFeatureIdentifier extends ValueObject<NativeFeatureIdentifierProps> {
|
||||||
|
static readonly TYPES = {
|
||||||
|
DailyEmailBackup: 'org.standardnotes.daily-email-backup',
|
||||||
|
Files: 'org.standardnotes.files',
|
||||||
|
FilesLowStorageTier: 'org.standardnotes.files-low-storage-tier',
|
||||||
|
FilesMaximumStorageTier: 'org.standardnotes.files-max-storage-tier',
|
||||||
|
ListedCustomDomain: 'org.standardnotes.listed-custom-domain',
|
||||||
|
NoteHistory30Days: 'org.standardnotes.note-history-30',
|
||||||
|
NoteHistory365Days: 'org.standardnotes.note-history-365',
|
||||||
|
NoteHistoryUnlimited: 'org.standardnotes.note-history-unlimited',
|
||||||
|
SignInAlerts: 'com.standardnotes.sign-in-alerts',
|
||||||
|
SmartFilters: 'org.standardnotes.smart-filters',
|
||||||
|
TagNesting: 'org.standardnotes.tag-nesting',
|
||||||
|
TwoFactorAuth: 'org.standardnotes.two-factor-auth',
|
||||||
|
UniversalSecondFactor: 'org.standardnotes.universal-second-factor',
|
||||||
|
SubscriptionSharing: 'org.standardnotes.subscription-sharing',
|
||||||
|
|
||||||
|
AutobiographyTheme: 'org.standardnotes.theme-autobiography',
|
||||||
|
DynamicTheme: 'org.standardnotes.theme-dynamic',
|
||||||
|
DarkTheme: 'org.standardnotes.theme-focus',
|
||||||
|
FuturaTheme: 'org.standardnotes.theme-futura',
|
||||||
|
MidnightTheme: 'org.standardnotes.theme-midnight',
|
||||||
|
SolarizedDarkTheme: 'org.standardnotes.theme-solarized-dark',
|
||||||
|
TitaniumTheme: 'org.standardnotes.theme-titanium',
|
||||||
|
|
||||||
|
PlainEditor: 'com.standardnotes.plain-text',
|
||||||
|
SuperEditor: 'com.standardnotes.super-editor',
|
||||||
|
|
||||||
|
CodeEditor: 'org.standardnotes.code-editor',
|
||||||
|
MarkdownProEditor: 'org.standardnotes.advanced-markdown-editor',
|
||||||
|
PlusEditor: 'org.standardnotes.plus-editor',
|
||||||
|
SheetsEditor: 'org.standardnotes.standard-sheets',
|
||||||
|
TaskEditor: 'org.standardnotes.simple-task-editor',
|
||||||
|
TokenVaultEditor: 'org.standardnotes.token-vault',
|
||||||
|
|
||||||
|
Clipper: 'org.standardnotes.clipper',
|
||||||
|
|
||||||
|
DeprecatedMarkdownVisualEditor: 'org.standardnotes.markdown-visual-editor',
|
||||||
|
DeprecatedBoldEditor: 'org.standardnotes.bold-editor',
|
||||||
|
DeprecatedMarkdownBasicEditor: 'org.standardnotes.simple-markdown-editor',
|
||||||
|
DeprecatedMarkdownMathEditor: 'org.standardnotes.fancy-markdown-editor',
|
||||||
|
DeprecatedMarkdownMinimistEditor: 'org.standardnotes.minimal-markdown-editor',
|
||||||
|
DeprecatedFoldersComponent: 'org.standardnotes.folders',
|
||||||
|
DeprecatedFileSafe: 'org.standardnotes.file-safe',
|
||||||
|
LegacyFileSafeIdentifier: 'org.standardnotes.legacy.file-safe',
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: NativeFeatureIdentifierProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(type: string): Result<NativeFeatureIdentifier> {
|
||||||
|
const isValidType = Object.values(this.TYPES).includes(type)
|
||||||
|
if (!isValidType) {
|
||||||
|
return Result.fail<NativeFeatureIdentifier>(`Invalid feature identifier: ${type}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<NativeFeatureIdentifier>(new NativeFeatureIdentifier({ value: type }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier for standalone filesafe instance offered as legacy installable via extensions-server
|
||||||
|
*/
|
||||||
|
export const ExperimentalFeatures = []
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from './FeatureIdentifier'
|
|
||||||
import { RoleFields } from './RoleFields'
|
import { RoleFields } from './RoleFields'
|
||||||
|
|
||||||
export type ServerFeatureDescription = RoleFields & {
|
export type ServerFeatureDescription = RoleFields & {
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
identifier: FeatureIdentifier
|
identifier: string
|
||||||
permission_name: PermissionName
|
permission_name: PermissionName
|
||||||
deprecated?: boolean
|
deprecated?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { RoleName } from '@standardnotes/domain-core'
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
import { ClientFeatureDescription } from '../Feature/ClientFeatureDescription'
|
import { ClientFeatureDescription } from '../Feature/ClientFeatureDescription'
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export function clientFeatures(): ClientFeatureDescription[] {
|
|||||||
{
|
{
|
||||||
name: 'Tag Nesting',
|
name: 'Tag Nesting',
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
identifier: FeatureIdentifier.TagNesting,
|
identifier: NativeFeatureIdentifier.TYPES.TagNesting,
|
||||||
permission_name: PermissionName.TagNesting,
|
permission_name: PermissionName.TagNesting,
|
||||||
description: 'Organize your tags into folders.',
|
description: 'Organize your tags into folders.',
|
||||||
},
|
},
|
||||||
@@ -16,22 +16,22 @@ export function clientFeatures(): ClientFeatureDescription[] {
|
|||||||
{
|
{
|
||||||
name: 'Smart Filters',
|
name: 'Smart Filters',
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
identifier: FeatureIdentifier.SmartFilters,
|
identifier: NativeFeatureIdentifier.TYPES.SmartFilters,
|
||||||
permission_name: PermissionName.SmartFilters,
|
permission_name: PermissionName.SmartFilters,
|
||||||
description: 'Create smart filters for viewing notes matching specific criteria.',
|
description: 'Create smart filters for viewing notes matching specific criteria.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Encrypted files',
|
name: 'Encrypted files',
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
identifier: FeatureIdentifier.Files,
|
identifier: NativeFeatureIdentifier.TYPES.Files,
|
||||||
permission_name: PermissionName.Files,
|
permission_name: PermissionName.Files,
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Extension',
|
name: 'Clipper',
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
identifier: FeatureIdentifier.Extension,
|
identifier: NativeFeatureIdentifier.TYPES.Clipper,
|
||||||
permission_name: PermissionName.Extension,
|
permission_name: PermissionName.Clipper,
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { EditorFeatureDescription } from '../Feature/EditorFeatureDescription'
|
|||||||
import { IframeComponentFeatureDescription } from '../Feature/IframeComponentFeatureDescription'
|
import { IframeComponentFeatureDescription } from '../Feature/IframeComponentFeatureDescription'
|
||||||
import { ContentType, RoleName } from '@standardnotes/domain-core'
|
import { ContentType, RoleName } from '@standardnotes/domain-core'
|
||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { NoteType } from '../Component/NoteType'
|
import { NoteType } from '../Component/NoteType'
|
||||||
import { FillIframeEditorDefaults } from './Utilities/FillEditorComponentDefaults'
|
import { FillIframeEditorDefaults } from './Utilities/FillEditorComponentDefaults'
|
||||||
import { ComponentAction } from '../Component/ComponentAction'
|
import { ComponentAction } from '../Component/ComponentAction'
|
||||||
@@ -12,7 +12,7 @@ import { ComponentArea } from '../Component/ComponentArea'
|
|||||||
export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
||||||
const bold: EditorFeatureDescription = FillIframeEditorDefaults({
|
const bold: EditorFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'Alternative Rich Text',
|
name: 'Alternative Rich Text',
|
||||||
identifier: FeatureIdentifier.DeprecatedBoldEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||||
note_type: NoteType.RichText,
|
note_type: NoteType.RichText,
|
||||||
file_type: 'html',
|
file_type: 'html',
|
||||||
component_permissions: [
|
component_permissions: [
|
||||||
@@ -39,7 +39,7 @@ export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
|||||||
|
|
||||||
const markdownBasic: EditorFeatureDescription = FillIframeEditorDefaults({
|
const markdownBasic: EditorFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'Basic Markdown',
|
name: 'Basic Markdown',
|
||||||
identifier: FeatureIdentifier.DeprecatedMarkdownBasicEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedMarkdownBasicEditor,
|
||||||
note_type: NoteType.Markdown,
|
note_type: NoteType.Markdown,
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
file_type: 'md',
|
file_type: 'md',
|
||||||
@@ -52,7 +52,7 @@ export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
|||||||
|
|
||||||
const markdownAlt: EditorFeatureDescription = FillIframeEditorDefaults({
|
const markdownAlt: EditorFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'Markdown Alternative',
|
name: 'Markdown Alternative',
|
||||||
identifier: FeatureIdentifier.DeprecatedMarkdownVisualEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedMarkdownVisualEditor,
|
||||||
note_type: NoteType.Markdown,
|
note_type: NoteType.Markdown,
|
||||||
file_type: 'md',
|
file_type: 'md',
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
@@ -66,7 +66,7 @@ export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
|||||||
|
|
||||||
const markdownMinimist: EditorFeatureDescription = FillIframeEditorDefaults({
|
const markdownMinimist: EditorFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'Minimal Markdown',
|
name: 'Minimal Markdown',
|
||||||
identifier: FeatureIdentifier.DeprecatedMarkdownMinimistEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedMarkdownMinimistEditor,
|
||||||
note_type: NoteType.Markdown,
|
note_type: NoteType.Markdown,
|
||||||
file_type: 'md',
|
file_type: 'md',
|
||||||
index_path: 'index.html',
|
index_path: 'index.html',
|
||||||
@@ -80,7 +80,7 @@ export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
|||||||
|
|
||||||
const markdownMath: EditorFeatureDescription = FillIframeEditorDefaults({
|
const markdownMath: EditorFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'Markdown with Math',
|
name: 'Markdown with Math',
|
||||||
identifier: FeatureIdentifier.DeprecatedMarkdownMathEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedMarkdownMathEditor,
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
permission_name: PermissionName.MarkdownMathEditor,
|
permission_name: PermissionName.MarkdownMathEditor,
|
||||||
note_type: NoteType.Markdown,
|
note_type: NoteType.Markdown,
|
||||||
@@ -94,7 +94,7 @@ export function GetDeprecatedFeatures(): AnyFeatureDescription[] {
|
|||||||
|
|
||||||
const filesafe: IframeComponentFeatureDescription = FillIframeEditorDefaults({
|
const filesafe: IframeComponentFeatureDescription = FillIframeEditorDefaults({
|
||||||
name: 'FileSafe',
|
name: 'FileSafe',
|
||||||
identifier: FeatureIdentifier.DeprecatedFileSafe,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedFileSafe,
|
||||||
component_permissions: [
|
component_permissions: [
|
||||||
{
|
{
|
||||||
name: ComponentAction.StreamContextItem,
|
name: ComponentAction.StreamContextItem,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { NoteType } from '../Component/NoteType'
|
import { NoteType } from '../Component/NoteType'
|
||||||
import { FillIframeEditorDefaults } from './Utilities/FillEditorComponentDefaults'
|
import { FillIframeEditorDefaults } from './Utilities/FillEditorComponentDefaults'
|
||||||
import { RoleName } from '@standardnotes/domain-core'
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
@@ -9,7 +9,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
const code = FillIframeEditorDefaults({
|
const code = FillIframeEditorDefaults({
|
||||||
name: 'Code',
|
name: 'Code',
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
identifier: FeatureIdentifier.CodeEditor,
|
identifier: NativeFeatureIdentifier.TYPES.CodeEditor,
|
||||||
permission_name: PermissionName.CodeEditor,
|
permission_name: PermissionName.CodeEditor,
|
||||||
note_type: NoteType.Code,
|
note_type: NoteType.Code,
|
||||||
file_type: 'txt',
|
file_type: 'txt',
|
||||||
@@ -26,7 +26,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
name: 'Rich Text',
|
name: 'Rich Text',
|
||||||
note_type: NoteType.RichText,
|
note_type: NoteType.RichText,
|
||||||
file_type: 'html',
|
file_type: 'html',
|
||||||
identifier: FeatureIdentifier.PlusEditor,
|
identifier: NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
permission_name: PermissionName.PlusEditor,
|
permission_name: PermissionName.PlusEditor,
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
description:
|
description:
|
||||||
@@ -37,7 +37,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
|
|
||||||
const markdown = FillIframeEditorDefaults({
|
const markdown = FillIframeEditorDefaults({
|
||||||
name: 'Markdown',
|
name: 'Markdown',
|
||||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
note_type: NoteType.Markdown,
|
note_type: NoteType.Markdown,
|
||||||
file_type: 'md',
|
file_type: 'md',
|
||||||
permission_name: PermissionName.MarkdownProEditor,
|
permission_name: PermissionName.MarkdownProEditor,
|
||||||
@@ -50,7 +50,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
|
|
||||||
const task = FillIframeEditorDefaults({
|
const task = FillIframeEditorDefaults({
|
||||||
name: 'Checklist',
|
name: 'Checklist',
|
||||||
identifier: FeatureIdentifier.TaskEditor,
|
identifier: NativeFeatureIdentifier.TYPES.TaskEditor,
|
||||||
note_type: NoteType.Task,
|
note_type: NoteType.Task,
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
file_type: 'md',
|
file_type: 'md',
|
||||||
@@ -67,7 +67,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
note_type: NoteType.Authentication,
|
note_type: NoteType.Authentication,
|
||||||
file_type: 'json',
|
file_type: 'json',
|
||||||
interchangeable: false,
|
interchangeable: false,
|
||||||
identifier: FeatureIdentifier.TokenVaultEditor,
|
identifier: NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||||
permission_name: PermissionName.TokenVaultEditor,
|
permission_name: PermissionName.TokenVaultEditor,
|
||||||
description:
|
description:
|
||||||
'Encrypt and protect your 2FA secrets for all your internet accounts. Authenticator handles your 2FA secrets so that you never lose them again, or have to start over when you get a new device.',
|
'Encrypt and protect your 2FA secrets for all your internet accounts. Authenticator handles your 2FA secrets so that you never lose them again, or have to start over when you get a new device.',
|
||||||
@@ -77,7 +77,7 @@ export function IframeEditors(): IframeComponentFeatureDescription[] {
|
|||||||
|
|
||||||
const spreadsheets = FillIframeEditorDefaults({
|
const spreadsheets = FillIframeEditorDefaults({
|
||||||
name: 'Spreadsheet',
|
name: 'Spreadsheet',
|
||||||
identifier: FeatureIdentifier.SheetsEditor,
|
identifier: NativeFeatureIdentifier.TYPES.SheetsEditor,
|
||||||
note_type: NoteType.Spreadsheet,
|
note_type: NoteType.Spreadsheet,
|
||||||
file_type: 'json',
|
file_type: 'json',
|
||||||
interchangeable: false,
|
interchangeable: false,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { RoleName } from '@standardnotes/domain-core'
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
import { NoteType } from '../Component/NoteType'
|
import { NoteType } from '../Component/NoteType'
|
||||||
import { EditorFeatureDescription } from '../Feature/EditorFeatureDescription'
|
import { EditorFeatureDescription } from '../Feature/EditorFeatureDescription'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
|
|
||||||
export function nativeEditors(): EditorFeatureDescription[] {
|
export function nativeEditors(): EditorFeatureDescription[] {
|
||||||
@@ -9,7 +9,7 @@ export function nativeEditors(): EditorFeatureDescription[] {
|
|||||||
{
|
{
|
||||||
name: 'Super',
|
name: 'Super',
|
||||||
note_type: NoteType.Super,
|
note_type: NoteType.Super,
|
||||||
identifier: FeatureIdentifier.SuperEditor,
|
identifier: NativeFeatureIdentifier.TYPES.SuperEditor,
|
||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
file_type: 'json',
|
file_type: 'json',
|
||||||
interchangeable: false,
|
interchangeable: false,
|
||||||
@@ -24,7 +24,7 @@ export function nativeEditors(): EditorFeatureDescription[] {
|
|||||||
spellcheckControl: true,
|
spellcheckControl: true,
|
||||||
file_type: 'txt',
|
file_type: 'txt',
|
||||||
interchangeable: true,
|
interchangeable: true,
|
||||||
identifier: FeatureIdentifier.PlainEditor,
|
identifier: NativeFeatureIdentifier.TYPES.PlainEditor,
|
||||||
availableInRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
permission_name: PermissionName.PlainEditor,
|
permission_name: PermissionName.PlainEditor,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
import { ServerFeatureDescription } from '../Feature/ServerFeatureDescription'
|
import { ServerFeatureDescription } from '../Feature/ServerFeatureDescription'
|
||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { RoleName } from '@standardnotes/domain-core'
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export function serverFeatures(): ServerFeatureDescription[] {
|
export function serverFeatures(): ServerFeatureDescription[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Two factor authentication',
|
name: 'Two factor authentication',
|
||||||
identifier: FeatureIdentifier.TwoFactorAuth,
|
identifier: NativeFeatureIdentifier.TYPES.TwoFactorAuth,
|
||||||
permission_name: PermissionName.TwoFactorAuth,
|
permission_name: PermissionName.TwoFactorAuth,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'U2F authentication',
|
name: 'U2F authentication',
|
||||||
identifier: FeatureIdentifier.UniversalSecondFactor,
|
identifier: NativeFeatureIdentifier.TYPES.UniversalSecondFactor,
|
||||||
permission_name: PermissionName.UniversalSecondFactor,
|
permission_name: PermissionName.UniversalSecondFactor,
|
||||||
availableInRoles: [RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Unlimited note history',
|
name: 'Unlimited note history',
|
||||||
identifier: FeatureIdentifier.NoteHistoryUnlimited,
|
identifier: NativeFeatureIdentifier.TYPES.NoteHistoryUnlimited,
|
||||||
permission_name: PermissionName.NoteHistoryUnlimited,
|
permission_name: PermissionName.NoteHistoryUnlimited,
|
||||||
availableInRoles: [RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '365 days note history',
|
name: '365 days note history',
|
||||||
identifier: FeatureIdentifier.NoteHistory365Days,
|
identifier: NativeFeatureIdentifier.TYPES.NoteHistory365Days,
|
||||||
permission_name: PermissionName.NoteHistory365Days,
|
permission_name: PermissionName.NoteHistory365Days,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser],
|
availableInRoles: [RoleName.NAMES.PlusUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Email backups',
|
name: 'Email backups',
|
||||||
identifier: FeatureIdentifier.DailyEmailBackup,
|
identifier: NativeFeatureIdentifier.TYPES.DailyEmailBackup,
|
||||||
permission_name: PermissionName.DailyEmailBackup,
|
permission_name: PermissionName.DailyEmailBackup,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Sign-in email alerts',
|
name: 'Sign-in email alerts',
|
||||||
identifier: FeatureIdentifier.SignInAlerts,
|
identifier: NativeFeatureIdentifier.TYPES.SignInAlerts,
|
||||||
permission_name: PermissionName.SignInAlerts,
|
permission_name: PermissionName.SignInAlerts,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Files maximum storage tier',
|
name: 'Files maximum storage tier',
|
||||||
identifier: FeatureIdentifier.FilesMaximumStorageTier,
|
identifier: NativeFeatureIdentifier.TYPES.FilesMaximumStorageTier,
|
||||||
permission_name: PermissionName.FilesMaximumStorageTier,
|
permission_name: PermissionName.FilesMaximumStorageTier,
|
||||||
availableInRoles: [RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Files low storage tier',
|
name: 'Files low storage tier',
|
||||||
identifier: FeatureIdentifier.FilesLowStorageTier,
|
identifier: NativeFeatureIdentifier.TYPES.FilesLowStorageTier,
|
||||||
permission_name: PermissionName.FilesLowStorageTier,
|
permission_name: PermissionName.FilesLowStorageTier,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser],
|
availableInRoles: [RoleName.NAMES.PlusUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Files medium storage tier',
|
name: 'Files medium storage tier',
|
||||||
identifier: FeatureIdentifier.SubscriptionSharing,
|
identifier: NativeFeatureIdentifier.TYPES.SubscriptionSharing,
|
||||||
permission_name: PermissionName.SubscriptionSharing,
|
permission_name: PermissionName.SubscriptionSharing,
|
||||||
availableInRoles: [RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Listed Custom Domain',
|
name: 'Listed Custom Domain',
|
||||||
identifier: FeatureIdentifier.ListedCustomDomain,
|
identifier: NativeFeatureIdentifier.TYPES.ListedCustomDomain,
|
||||||
permission_name: PermissionName.ListedCustomDomain,
|
permission_name: PermissionName.ListedCustomDomain,
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { ThemeFeatureDescription } from '../Feature/ThemeFeatureDescription'
|
import { ThemeFeatureDescription } from '../Feature/ThemeFeatureDescription'
|
||||||
import { PermissionName } from '../Permission/PermissionName'
|
import { PermissionName } from '../Permission/PermissionName'
|
||||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
import { NativeFeatureIdentifier } from '../Feature/NativeFeatureIdentifier'
|
||||||
import { FillThemeComponentDefaults } from './Utilities/FillThemeComponentDefaults'
|
import { FillThemeComponentDefaults } from './Utilities/FillThemeComponentDefaults'
|
||||||
import { RoleName } from '@standardnotes/domain-core'
|
import { RoleName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export function themes(): ThemeFeatureDescription[] {
|
export function themes(): ThemeFeatureDescription[] {
|
||||||
const midnight: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const midnight: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
name: 'Midnight',
|
name: 'Midnight',
|
||||||
identifier: FeatureIdentifier.MidnightTheme,
|
identifier: NativeFeatureIdentifier.TYPES.MidnightTheme,
|
||||||
permission_name: PermissionName.MidnightTheme,
|
permission_name: PermissionName.MidnightTheme,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
dock_icon: {
|
dock_icon: {
|
||||||
@@ -22,7 +22,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const futura: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const futura: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Futura',
|
name: 'Futura',
|
||||||
identifier: FeatureIdentifier.FuturaTheme,
|
identifier: NativeFeatureIdentifier.TYPES.FuturaTheme,
|
||||||
permission_name: PermissionName.FuturaTheme,
|
permission_name: PermissionName.FuturaTheme,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
dock_icon: {
|
dock_icon: {
|
||||||
@@ -36,7 +36,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const solarizedDark: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const solarizedDark: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Solarized Dark',
|
name: 'Solarized Dark',
|
||||||
identifier: FeatureIdentifier.SolarizedDarkTheme,
|
identifier: NativeFeatureIdentifier.TYPES.SolarizedDarkTheme,
|
||||||
permission_name: PermissionName.SolarizedDarkTheme,
|
permission_name: PermissionName.SolarizedDarkTheme,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
dock_icon: {
|
dock_icon: {
|
||||||
@@ -50,7 +50,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const autobiography: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const autobiography: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Autobiography',
|
name: 'Autobiography',
|
||||||
identifier: FeatureIdentifier.AutobiographyTheme,
|
identifier: NativeFeatureIdentifier.TYPES.AutobiographyTheme,
|
||||||
permission_name: PermissionName.AutobiographyTheme,
|
permission_name: PermissionName.AutobiographyTheme,
|
||||||
dock_icon: {
|
dock_icon: {
|
||||||
type: 'circle',
|
type: 'circle',
|
||||||
@@ -63,7 +63,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const dark: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const dark: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Dark',
|
name: 'Dark',
|
||||||
identifier: FeatureIdentifier.DarkTheme,
|
identifier: NativeFeatureIdentifier.TYPES.DarkTheme,
|
||||||
permission_name: PermissionName.FocusedTheme,
|
permission_name: PermissionName.FocusedTheme,
|
||||||
clientControlled: true,
|
clientControlled: true,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
@@ -78,7 +78,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const titanium: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const titanium: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Titanium',
|
name: 'Titanium',
|
||||||
identifier: FeatureIdentifier.TitaniumTheme,
|
identifier: NativeFeatureIdentifier.TYPES.TitaniumTheme,
|
||||||
permission_name: PermissionName.TitaniumTheme,
|
permission_name: PermissionName.TitaniumTheme,
|
||||||
dock_icon: {
|
dock_icon: {
|
||||||
type: 'circle',
|
type: 'circle',
|
||||||
@@ -91,7 +91,7 @@ export function themes(): ThemeFeatureDescription[] {
|
|||||||
const dynamic: ThemeFeatureDescription = FillThemeComponentDefaults({
|
const dynamic: ThemeFeatureDescription = FillThemeComponentDefaults({
|
||||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||||
name: 'Dynamic Panels',
|
name: 'Dynamic Panels',
|
||||||
identifier: FeatureIdentifier.DynamicTheme,
|
identifier: NativeFeatureIdentifier.TYPES.DynamicTheme,
|
||||||
permission_name: PermissionName.ThemeDynamic,
|
permission_name: PermissionName.ThemeDynamic,
|
||||||
layerable: true,
|
layerable: true,
|
||||||
no_mobile: true,
|
no_mobile: true,
|
||||||
|
|||||||
@@ -37,5 +37,5 @@ export enum PermissionName {
|
|||||||
UniversalSecondFactor = 'server:universal-second-factor',
|
UniversalSecondFactor = 'server:universal-second-factor',
|
||||||
SubscriptionSharing = 'server:subscription-sharing',
|
SubscriptionSharing = 'server:subscription-sharing',
|
||||||
SuperEditor = 'editor:super-editor',
|
SuperEditor = 'editor:super-editor',
|
||||||
Extension = 'app:extension',
|
Clipper = 'app:clipper',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export * from './Feature/AnyFeatureDescription'
|
export * from './Feature/AnyFeatureDescription'
|
||||||
export * from './Feature/FeatureIdentifier'
|
export * from './Feature/NativeFeatureIdentifier'
|
||||||
export * from './Feature/Features'
|
export * from './Feature/Features'
|
||||||
export * from './Feature/TypeGuards'
|
export * from './Feature/TypeGuards'
|
||||||
|
|
||||||
@@ -22,4 +22,3 @@ export * from './Component/ComponentFlag'
|
|||||||
export * from './Component/ComponentPermission'
|
export * from './Component/ComponentPermission'
|
||||||
export * from './Component/NoteType'
|
export * from './Component/NoteType'
|
||||||
export * from './Component/ThemeDockIcon'
|
export * from './Component/ThemeDockIcon'
|
||||||
export * from './Component/EditorIdentifier'
|
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import {
|
|||||||
NoteType,
|
NoteType,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import {
|
import { isUIFeatureAnIframeFeature, isItemBasedFeature, isNativeFeature } from './TypeGuards'
|
||||||
isUIFeatureAnIframeFeature,
|
|
||||||
isComponentOrFeatureDescriptionAComponent,
|
|
||||||
isComponentOrFeatureDescriptionAFeatureDescription,
|
|
||||||
} from './TypeGuards'
|
|
||||||
import { UIFeature } from './UIFeature'
|
import { UIFeature } from './UIFeature'
|
||||||
import { ComponentInterface } from '../../Syncable/Component'
|
import { ComponentInterface } from '../../Syncable/Component'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
@@ -45,7 +41,7 @@ describe('TypeGuards', () => {
|
|||||||
uuid: 'abc-123',
|
uuid: 'abc-123',
|
||||||
} as ComponentInterface
|
} as ComponentInterface
|
||||||
|
|
||||||
expect(isComponentOrFeatureDescriptionAComponent(x)).toBe(true)
|
expect(isItemBasedFeature(x)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if feature description is not a component', () => {
|
it('should return false if feature description is not a component', () => {
|
||||||
@@ -53,17 +49,17 @@ describe('TypeGuards', () => {
|
|||||||
note_type: NoteType.Super,
|
note_type: NoteType.Super,
|
||||||
} as jest.Mocked<EditorFeatureDescription>
|
} as jest.Mocked<EditorFeatureDescription>
|
||||||
|
|
||||||
expect(isComponentOrFeatureDescriptionAComponent(x)).toBe(false)
|
expect(isItemBasedFeature(x)).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('isComponentOrFeatureDescriptionAFeatureDescription', () => {
|
describe('isNativeFeature', () => {
|
||||||
it('should return true if x is a feature description', () => {
|
it('should return true if x is a feature description', () => {
|
||||||
const x: AnyFeatureDescription = {
|
const x: AnyFeatureDescription = {
|
||||||
content_type: 'TestContentType',
|
content_type: 'TestContentType',
|
||||||
} as AnyFeatureDescription
|
} as AnyFeatureDescription
|
||||||
|
|
||||||
expect(isComponentOrFeatureDescriptionAFeatureDescription(x)).toBe(true)
|
expect(isNativeFeature(x)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if x is a component', () => {
|
it('should return false if x is a component', () => {
|
||||||
@@ -71,7 +67,7 @@ describe('TypeGuards', () => {
|
|||||||
uuid: 'abc-123',
|
uuid: 'abc-123',
|
||||||
} as ComponentInterface
|
} as ComponentInterface
|
||||||
|
|
||||||
expect(isComponentOrFeatureDescriptionAFeatureDescription(x)).toBe(false)
|
expect(isNativeFeature(x)).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,14 +14,10 @@ export function isUIFeatureAnIframeFeature(
|
|||||||
return isIframeComponentFeatureDescription(x.featureDescription)
|
return isIframeComponentFeatureDescription(x.featureDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isComponentOrFeatureDescriptionAComponent(
|
export function isItemBasedFeature(x: ComponentInterface | UIFeatureDescriptionTypes): x is ComponentInterface {
|
||||||
x: ComponentInterface | UIFeatureDescriptionTypes,
|
|
||||||
): x is ComponentInterface {
|
|
||||||
return 'uuid' in x
|
return 'uuid' in x
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isComponentOrFeatureDescriptionAFeatureDescription(
|
export function isNativeFeature(x: ComponentInterface | AnyFeatureDescription): x is AnyFeatureDescription {
|
||||||
x: ComponentInterface | AnyFeatureDescription,
|
|
||||||
): x is AnyFeatureDescription {
|
|
||||||
return !('uuid' in x)
|
return !('uuid' in x)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
ComponentArea,
|
ComponentArea,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
NoteType,
|
NoteType,
|
||||||
ThemeDockIcon,
|
ThemeDockIcon,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
@@ -12,29 +12,27 @@ import {
|
|||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { ComponentInterface } from '../../Syncable/Component/ComponentInterface'
|
import { ComponentInterface } from '../../Syncable/Component/ComponentInterface'
|
||||||
import { isTheme } from '../../Syncable/Theme'
|
import { isTheme } from '../../Syncable/Theme'
|
||||||
import {
|
import { isItemBasedFeature, isNativeFeature } from './TypeGuards'
|
||||||
isComponentOrFeatureDescriptionAComponent,
|
|
||||||
isComponentOrFeatureDescriptionAFeatureDescription,
|
|
||||||
} from './TypeGuards'
|
|
||||||
import { UIFeatureInterface } from './UIFeatureInterface'
|
import { UIFeatureInterface } from './UIFeatureInterface'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeatureInterface<F> {
|
export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeatureInterface<F> {
|
||||||
constructor(public readonly item: ComponentInterface | F) {}
|
constructor(public readonly item: ComponentInterface | F) {}
|
||||||
|
|
||||||
get isComponent(): boolean {
|
get isComponent(): boolean {
|
||||||
return isComponentOrFeatureDescriptionAComponent(this.item)
|
return isItemBasedFeature(this.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFeatureDescription(): boolean {
|
get isFeatureDescription(): boolean {
|
||||||
return isComponentOrFeatureDescriptionAFeatureDescription(this.item)
|
return isNativeFeature(this.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isThemeComponent(): boolean {
|
get isThemeComponent(): boolean {
|
||||||
return isComponentOrFeatureDescriptionAComponent(this.item) && isTheme(this.item)
|
return isItemBasedFeature(this.item) && isTheme(this.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
get asComponent(): ComponentInterface {
|
get asComponent(): ComponentInterface {
|
||||||
if (isComponentOrFeatureDescriptionAComponent(this.item)) {
|
if (isItemBasedFeature(this.item)) {
|
||||||
return this.item
|
return this.item
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,29 +40,30 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get asFeatureDescription(): F {
|
get asFeatureDescription(): F {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item
|
return this.item
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Cannot cast item to feature description')
|
throw new Error('Cannot cast item to feature description')
|
||||||
}
|
}
|
||||||
|
|
||||||
get uniqueIdentifier(): string {
|
get uniqueIdentifier(): NativeFeatureIdentifier | Uuid {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item.identifier
|
const nativeFeature = NativeFeatureIdentifier.create(this.item.identifier)
|
||||||
|
return nativeFeature.getValue()
|
||||||
} else {
|
} else {
|
||||||
return this.item.uuid
|
return Uuid.create(this.item.uuid).getValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get featureIdentifier(): FeatureIdentifier {
|
get featureIdentifier(): string {
|
||||||
return this.item.identifier
|
return this.item.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
get noteType(): NoteType {
|
get noteType(): NoteType {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item) && isEditorFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item) && isEditorFeatureDescription(this.item)) {
|
||||||
return this.item.note_type ?? NoteType.Unknown
|
return this.item.note_type ?? NoteType.Unknown
|
||||||
} else if (isComponentOrFeatureDescriptionAComponent(this.item)) {
|
} else if (isItemBasedFeature(this.item)) {
|
||||||
return this.item.noteType
|
return this.item.noteType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +71,9 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get fileType(): EditorFeatureDescription['file_type'] {
|
get fileType(): EditorFeatureDescription['file_type'] {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item) && isEditorFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item) && isEditorFeatureDescription(this.item)) {
|
||||||
return this.item.file_type
|
return this.item.file_type
|
||||||
} else if (
|
} else if (isItemBasedFeature(this.item) && isEditorFeatureDescription(this.item.package_info)) {
|
||||||
isComponentOrFeatureDescriptionAComponent(this.item) &&
|
|
||||||
isEditorFeatureDescription(this.item.package_info)
|
|
||||||
) {
|
|
||||||
return this.item.package_info?.file_type ?? 'txt'
|
return this.item.package_info?.file_type ?? 'txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +81,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get displayName(): string {
|
get displayName(): string {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item.name ?? ''
|
return this.item.name ?? ''
|
||||||
} else {
|
} else {
|
||||||
return this.item.displayName
|
return this.item.displayName
|
||||||
@@ -93,7 +89,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get description(): string {
|
get description(): string {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item.description ?? ''
|
return this.item.description ?? ''
|
||||||
} else {
|
} else {
|
||||||
return this.item.package_info.description ?? ''
|
return this.item.package_info.description ?? ''
|
||||||
@@ -101,7 +97,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get deprecationMessage(): string | undefined {
|
get deprecationMessage(): string | undefined {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item.deprecation_message
|
return this.item.deprecation_message
|
||||||
} else {
|
} else {
|
||||||
return this.item.deprecationMessage
|
return this.item.deprecationMessage
|
||||||
@@ -109,7 +105,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get expirationDate(): Date | undefined {
|
get expirationDate(): Date | undefined {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item.expires_at ? new Date(this.item.expires_at) : undefined
|
return this.item.expires_at ? new Date(this.item.expires_at) : undefined
|
||||||
} else {
|
} else {
|
||||||
return this.item.valid_until
|
return this.item.valid_until
|
||||||
@@ -117,7 +113,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get featureDescription(): F {
|
get featureDescription(): F {
|
||||||
if (isComponentOrFeatureDescriptionAFeatureDescription(this.item)) {
|
if (isNativeFeature(this.item)) {
|
||||||
return this.item
|
return this.item
|
||||||
} else {
|
} else {
|
||||||
return this.item.package_info as F
|
return this.item.package_info as F
|
||||||
@@ -125,12 +121,9 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get acquiredPermissions(): ComponentPermission[] {
|
get acquiredPermissions(): ComponentPermission[] {
|
||||||
if (
|
if (isNativeFeature(this.item) && isIframeComponentFeatureDescription(this.item)) {
|
||||||
isComponentOrFeatureDescriptionAFeatureDescription(this.item) &&
|
|
||||||
isIframeComponentFeatureDescription(this.item)
|
|
||||||
) {
|
|
||||||
return this.item.component_permissions ?? []
|
return this.item.component_permissions ?? []
|
||||||
} else if (isComponentOrFeatureDescriptionAComponent(this.item)) {
|
} else if (isItemBasedFeature(this.item)) {
|
||||||
return this.item.permissions
|
return this.item.permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +139,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get layerable(): boolean {
|
get layerable(): boolean {
|
||||||
if (isComponentOrFeatureDescriptionAComponent(this.item) && isTheme(this.item)) {
|
if (isItemBasedFeature(this.item) && isTheme(this.item)) {
|
||||||
return this.item.layerable
|
return this.item.layerable
|
||||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||||
return this.asFeatureDescription.layerable ?? false
|
return this.asFeatureDescription.layerable ?? false
|
||||||
@@ -156,7 +149,7 @@ export class UIFeature<F extends UIFeatureDescriptionTypes> implements UIFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
get dockIcon(): ThemeDockIcon | undefined {
|
get dockIcon(): ThemeDockIcon | undefined {
|
||||||
if (isComponentOrFeatureDescriptionAComponent(this.item) && isTheme(this.item)) {
|
if (isItemBasedFeature(this.item) && isTheme(this.item)) {
|
||||||
return this.item.package_info.dock_icon
|
return this.item.package_info.dock_icon
|
||||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||||
return this.asFeatureDescription.dock_icon
|
return this.asFeatureDescription.dock_icon
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import {
|
|||||||
ComponentArea,
|
ComponentArea,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
NoteType,
|
NoteType,
|
||||||
ThemeDockIcon,
|
ThemeDockIcon,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { ComponentInterface } from '../../Syncable/Component'
|
import { ComponentInterface } from '../../Syncable/Component'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface UIFeatureInterface<F extends UIFeatureDescriptionTypes> {
|
export interface UIFeatureInterface<F extends UIFeatureDescriptionTypes> {
|
||||||
item: ComponentInterface | F
|
item: ComponentInterface | F
|
||||||
@@ -16,8 +17,8 @@ export interface UIFeatureInterface<F extends UIFeatureDescriptionTypes> {
|
|||||||
get isThemeComponent(): boolean
|
get isThemeComponent(): boolean
|
||||||
get asComponent(): ComponentInterface
|
get asComponent(): ComponentInterface
|
||||||
get asFeatureDescription(): F
|
get asFeatureDescription(): F
|
||||||
get uniqueIdentifier(): string
|
get uniqueIdentifier(): NativeFeatureIdentifier | Uuid
|
||||||
get featureIdentifier(): FeatureIdentifier
|
get featureIdentifier(): string
|
||||||
get noteType(): NoteType
|
get noteType(): NoteType
|
||||||
get fileType(): EditorFeatureDescription['file_type']
|
get fileType(): EditorFeatureDescription['file_type']
|
||||||
get displayName(): string
|
get displayName(): string
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { isValidUrl } from '@standardnotes/utils'
|
import { isValidUrl } from '@standardnotes/utils'
|
||||||
import {
|
import {
|
||||||
FeatureIdentifier,
|
|
||||||
ThirdPartyFeatureDescription,
|
ThirdPartyFeatureDescription,
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
ComponentFlag,
|
ComponentFlag,
|
||||||
@@ -175,7 +174,7 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
|||||||
return this.valid_until.getTime() > 0 && this.valid_until <= new Date()
|
return this.valid_until.getTime() > 0 && this.valid_until <= new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
public get identifier(): FeatureIdentifier {
|
public get identifier(): string {
|
||||||
return this.package_info.identifier
|
return this.package_info.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { ComponentArea, ComponentPermission, NoteType, ThirdPartyFeatureDescription } from '@standardnotes/features'
|
||||||
ComponentArea,
|
|
||||||
ComponentPermission,
|
|
||||||
FeatureIdentifier,
|
|
||||||
NoteType,
|
|
||||||
ThirdPartyFeatureDescription,
|
|
||||||
} from '@standardnotes/features'
|
|
||||||
import { ComponentPackageInfo } from './PackageInfo'
|
import { ComponentPackageInfo } from './PackageInfo'
|
||||||
import { DecryptedItemInterface } from '../../Abstract/Item'
|
import { DecryptedItemInterface } from '../../Abstract/Item'
|
||||||
import { ComponentContent } from './ComponentContent'
|
import { ComponentContent } from './ComponentContent'
|
||||||
@@ -35,7 +29,7 @@ export interface ComponentInterface extends DecryptedItemInterface<ComponentCont
|
|||||||
isExplicitlyDisabledForItem(uuid: string): boolean
|
isExplicitlyDisabledForItem(uuid: string): boolean
|
||||||
legacyIsDefaultEditor(): boolean
|
legacyIsDefaultEditor(): boolean
|
||||||
|
|
||||||
get identifier(): FeatureIdentifier
|
get identifier(): string
|
||||||
get noteType(): NoteType
|
get noteType(): NoteType
|
||||||
get displayName(): string
|
get displayName(): string
|
||||||
get deprecationMessage(): string | undefined
|
get deprecationMessage(): string | undefined
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppDataField } from './../../Abstract/Item/Types/AppDataField'
|
import { AppDataField } from './../../Abstract/Item/Types/AppDataField'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NoteType } from '@standardnotes/features'
|
||||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||||
@@ -21,7 +21,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
|||||||
public readonly authorizedForListed: boolean
|
public readonly authorizedForListed: boolean
|
||||||
|
|
||||||
/** The package_info.identifier of the editor (not its uuid), such as org.standardnotes.advanced-markdown */
|
/** The package_info.identifier of the editor (not its uuid), such as org.standardnotes.advanced-markdown */
|
||||||
public readonly editorIdentifier?: FeatureIdentifier | string
|
public readonly editorIdentifier?: string
|
||||||
|
|
||||||
constructor(payload: DecryptedPayloadInterface<NoteContent>) {
|
constructor(payload: DecryptedPayloadInterface<NoteContent>) {
|
||||||
super(payload)
|
super(payload)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NoteType } from '@standardnotes/features'
|
||||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||||
import { EditorLineWidth } from '../UserPrefs'
|
import { EditorLineWidth } from '../UserPrefs'
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export interface NoteContentSpecialized {
|
|||||||
spellcheck?: boolean
|
spellcheck?: boolean
|
||||||
editorWidth?: EditorLineWidth
|
editorWidth?: EditorLineWidth
|
||||||
noteType?: NoteType
|
noteType?: NoteType
|
||||||
editorIdentifier?: FeatureIdentifier | string
|
editorIdentifier?: string
|
||||||
authorizedForListed?: boolean
|
authorizedForListed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NoteMutator } from './NoteMutator'
|
import { NoteMutator } from './NoteMutator'
|
||||||
import { createNote } from './../../Utilities/Test/SpecUtils'
|
import { createNote } from './../../Utilities/Test/SpecUtils'
|
||||||
import { MutationType } from '../../Abstract/Item'
|
import { MutationType } from '../../Abstract/Item'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
|
|
||||||
describe('note mutator', () => {
|
describe('note mutator', () => {
|
||||||
it('sets noteType', () => {
|
it('sets noteType', () => {
|
||||||
@@ -16,9 +16,9 @@ describe('note mutator', () => {
|
|||||||
it('sets componentIdentifier', () => {
|
it('sets componentIdentifier', () => {
|
||||||
const note = createNote({})
|
const note = createNote({})
|
||||||
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
|
const mutator = new NoteMutator(note, MutationType.NoUpdateUserTimestamps)
|
||||||
mutator.editorIdentifier = FeatureIdentifier.MarkdownProEditor
|
mutator.editorIdentifier = NativeFeatureIdentifier.TYPES.MarkdownProEditor
|
||||||
const result = mutator.getResult()
|
const result = mutator.getResult()
|
||||||
|
|
||||||
expect(result.content.editorIdentifier).toEqual(FeatureIdentifier.MarkdownProEditor)
|
expect(result.content.editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.MarkdownProEditor)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemM
|
|||||||
import { SNNote } from './Note'
|
import { SNNote } from './Note'
|
||||||
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
|
import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReference'
|
||||||
import { ContentReferenceType } from '../../Abstract/Item'
|
import { ContentReferenceType } from '../../Abstract/Item'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NoteType } from '@standardnotes/features'
|
||||||
import { EditorLineWidth } from '../UserPrefs'
|
import { EditorLineWidth } from '../UserPrefs'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
|||||||
this.mutableContent.noteType = noteType
|
this.mutableContent.noteType = noteType
|
||||||
}
|
}
|
||||||
|
|
||||||
set editorIdentifier(identifier: FeatureIdentifier | string | undefined) {
|
set editorIdentifier(identifier: string | undefined) {
|
||||||
this.mutableContent.editorIdentifier = identifier
|
this.mutableContent.editorIdentifier = identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { EditorIdentifier } from '@standardnotes/features'
|
|
||||||
import { NewNoteTitleFormat } from '../UserPrefs'
|
import { NewNoteTitleFormat } from '../UserPrefs'
|
||||||
import { CollectionSortProperty } from './../../Runtime/Collection/CollectionSort'
|
import { CollectionSortProperty } from './../../Runtime/Collection/CollectionSort'
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ export interface TagPreferences {
|
|||||||
hideEditorIcon?: boolean
|
hideEditorIcon?: boolean
|
||||||
newNoteTitleFormat?: NewNoteTitleFormat
|
newNoteTitleFormat?: NewNoteTitleFormat
|
||||||
customNoteTitleFormat?: string
|
customNoteTitleFormat?: string
|
||||||
editorIdentifier?: EditorIdentifier
|
editorIdentifier?: string
|
||||||
entryMode?: 'normal' | 'daily'
|
entryMode?: 'normal' | 'daily'
|
||||||
panelWidth?: number
|
panelWidth?: number
|
||||||
useTableView?: boolean
|
useTableView?: boolean
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
export type AllComponentPreferences = Record<string, ComponentPreferencesEntry>
|
||||||
|
|
||||||
type UuidString = string
|
|
||||||
|
|
||||||
export type AllComponentPreferences = Record<FeatureIdentifier | UuidString, ComponentPreferencesEntry>
|
|
||||||
|
|
||||||
export type ComponentPreferencesEntry = Record<string, unknown>
|
export type ComponentPreferencesEntry = Record<string, unknown>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { CollectionSort } from '../../Runtime/Collection/CollectionSort'
|
import { CollectionSort } from '../../Runtime/Collection/CollectionSort'
|
||||||
import { EditorFontSize } from './EditorFontSize'
|
import { EditorFontSize } from './EditorFontSize'
|
||||||
import { EditorLineHeight } from './EditorLineHeight'
|
import { EditorLineHeight } from './EditorLineHeight'
|
||||||
@@ -29,7 +29,7 @@ export const PrefDefaults = {
|
|||||||
[PrefKey.NotesHideEditorIcon]: false,
|
[PrefKey.NotesHideEditorIcon]: false,
|
||||||
[PrefKey.UseSystemColorScheme]: false,
|
[PrefKey.UseSystemColorScheme]: false,
|
||||||
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: FeatureIdentifier.DarkTheme,
|
[PrefKey.AutoDarkThemeIdentifier]: NativeFeatureIdentifier.TYPES.DarkTheme,
|
||||||
[PrefKey.NoteAddToParentFolders]: true,
|
[PrefKey.NoteAddToParentFolders]: true,
|
||||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat.CurrentDateAndTime,
|
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat.CurrentDateAndTime,
|
||||||
[PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A',
|
[PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A',
|
||||||
@@ -37,7 +37,7 @@ export const PrefDefaults = {
|
|||||||
[PrefKey.PaneGesturesEnabled]: true,
|
[PrefKey.PaneGesturesEnabled]: true,
|
||||||
[PrefKey.MomentsDefaultTagUuid]: undefined,
|
[PrefKey.MomentsDefaultTagUuid]: undefined,
|
||||||
[PrefKey.ClipperDefaultTagUuid]: undefined,
|
[PrefKey.ClipperDefaultTagUuid]: undefined,
|
||||||
[PrefKey.DefaultEditorIdentifier]: FeatureIdentifier.PlainEditor,
|
[PrefKey.DefaultEditorIdentifier]: NativeFeatureIdentifier.TYPES.PlainEditor,
|
||||||
[PrefKey.SuperNoteExportFormat]: 'json',
|
[PrefKey.SuperNoteExportFormat]: 'json',
|
||||||
[PrefKey.SystemViewPreferences]: {},
|
[PrefKey.SystemViewPreferences]: {},
|
||||||
[PrefKey.AuthenticatorNames]: '',
|
[PrefKey.AuthenticatorNames]: '',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { CollectionSortProperty } from '../../Runtime/Collection/CollectionSort'
|
import { CollectionSortProperty } from '../../Runtime/Collection/CollectionSort'
|
||||||
import { EditorIdentifier, FeatureIdentifier } from '@standardnotes/features'
|
|
||||||
import { SystemViewId } from '../SmartView'
|
import { SystemViewId } from '../SmartView'
|
||||||
import { TagPreferences } from '../Tag'
|
import { TagPreferences } from '../Tag'
|
||||||
import { NewNoteTitleFormat } from './NewNoteTitleFormat'
|
import { NewNoteTitleFormat } from './NewNoteTitleFormat'
|
||||||
@@ -67,8 +66,8 @@ export type PrefValue = {
|
|||||||
[PrefKey.NotesHideTags]: boolean
|
[PrefKey.NotesHideTags]: boolean
|
||||||
[PrefKey.NotesHideEditorIcon]: boolean
|
[PrefKey.NotesHideEditorIcon]: boolean
|
||||||
[PrefKey.UseSystemColorScheme]: boolean
|
[PrefKey.UseSystemColorScheme]: boolean
|
||||||
[PrefKey.AutoLightThemeIdentifier]: FeatureIdentifier | 'Default' | 'Dark'
|
[PrefKey.AutoLightThemeIdentifier]: string
|
||||||
[PrefKey.AutoDarkThemeIdentifier]: FeatureIdentifier | 'Default' | 'Dark'
|
[PrefKey.AutoDarkThemeIdentifier]: string
|
||||||
[PrefKey.NoteAddToParentFolders]: boolean
|
[PrefKey.NoteAddToParentFolders]: boolean
|
||||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
|
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
|
||||||
[PrefKey.CustomNoteTitleFormat]: string
|
[PrefKey.CustomNoteTitleFormat]: string
|
||||||
@@ -76,7 +75,7 @@ export type PrefValue = {
|
|||||||
[PrefKey.EditorLineWidth]: EditorLineWidth
|
[PrefKey.EditorLineWidth]: EditorLineWidth
|
||||||
[PrefKey.EditorFontSize]: EditorFontSize
|
[PrefKey.EditorFontSize]: EditorFontSize
|
||||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||||
[PrefKey.DefaultEditorIdentifier]: EditorIdentifier
|
[PrefKey.DefaultEditorIdentifier]: string
|
||||||
[PrefKey.MomentsDefaultTagUuid]: string | undefined
|
[PrefKey.MomentsDefaultTagUuid]: string | undefined
|
||||||
[PrefKey.ClipperDefaultTagUuid]: string | undefined
|
[PrefKey.ClipperDefaultTagUuid]: string | undefined
|
||||||
[PrefKey.SystemViewPreferences]: Partial<Record<SystemViewId, TagPreferences>>
|
[PrefKey.SystemViewPreferences]: Partial<Record<SystemViewId, TagPreferences>>
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import {
|
|||||||
ComponentArea,
|
ComponentArea,
|
||||||
ComponentFeatureDescription,
|
ComponentFeatureDescription,
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
EditorIdentifier,
|
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
|
NativeFeatureIdentifier,
|
||||||
ThemeFeatureDescription,
|
ThemeFeatureDescription,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { ActionObserver, ComponentInterface, UIFeature, PermissionDialog, SNNote, SNTag } from '@standardnotes/models'
|
import { ActionObserver, ComponentInterface, UIFeature, PermissionDialog, SNNote, SNTag } from '@standardnotes/models'
|
||||||
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
|
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
|
||||||
import { ComponentViewerInterface } from './ComponentViewerInterface'
|
import { ComponentViewerInterface } from './ComponentViewerInterface'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface ComponentManagerInterface {
|
export interface ComponentManagerInterface {
|
||||||
urlForFeature(uiFeature: UIFeature<ComponentFeatureDescription>): string | undefined
|
urlForFeature(uiFeature: UIFeature<ComponentFeatureDescription>): string | undefined
|
||||||
@@ -31,12 +32,12 @@ export interface ComponentManagerInterface {
|
|||||||
setPermissionDialogUIHandler(handler: (dialog: PermissionDialog) => void): void
|
setPermissionDialogUIHandler(handler: (dialog: PermissionDialog) => void): void
|
||||||
|
|
||||||
editorForNote(note: SNNote): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription>
|
editorForNote(note: SNNote): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription>
|
||||||
getDefaultEditorIdentifier(currentTag?: SNTag): EditorIdentifier
|
getDefaultEditorIdentifier(currentTag?: SNTag): string
|
||||||
|
|
||||||
isThemeActive(theme: UIFeature<ThemeFeatureDescription>): boolean
|
isThemeActive(theme: UIFeature<ThemeFeatureDescription>): boolean
|
||||||
toggleTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void>
|
toggleTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void>
|
||||||
getActiveThemes(): UIFeature<ThemeFeatureDescription>[]
|
getActiveThemes(): UIFeature<ThemeFeatureDescription>[]
|
||||||
getActiveThemesIdentifiers(): string[]
|
getActiveThemesIdentifiers(): { features: NativeFeatureIdentifier[]; uuids: Uuid[] }
|
||||||
|
|
||||||
isComponentActive(component: ComponentInterface): boolean
|
isComponentActive(component: ComponentInterface): boolean
|
||||||
toggleComponent(component: ComponentInterface): Promise<void>
|
toggleComponent(component: ComponentInterface): Promise<void>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ActionObserver, ComponentEventObserver, ComponentMessage, UIFeature } from '@standardnotes/models'
|
import { ActionObserver, ComponentEventObserver, ComponentMessage, UIFeature } from '@standardnotes/models'
|
||||||
import { FeatureStatus } from '../Feature/FeatureStatus'
|
import { FeatureStatus } from '../Feature/FeatureStatus'
|
||||||
import { ComponentViewerError } from './ComponentViewerError'
|
import { ComponentViewerError } from './ComponentViewerError'
|
||||||
import { IframeComponentFeatureDescription } from '@standardnotes/features'
|
import { IframeComponentFeatureDescription, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface ComponentViewerInterface {
|
export interface ComponentViewerInterface {
|
||||||
readonly identifier: string
|
readonly identifier: string
|
||||||
@@ -9,7 +10,7 @@ export interface ComponentViewerInterface {
|
|||||||
readonly sessionKey?: string
|
readonly sessionKey?: string
|
||||||
|
|
||||||
get url(): string
|
get url(): string
|
||||||
get componentUniqueIdentifier(): string
|
get componentUniqueIdentifier(): NativeFeatureIdentifier | Uuid
|
||||||
|
|
||||||
getComponentOrFeatureItem(): UIFeature<IframeComponentFeatureDescription>
|
getComponentOrFeatureItem(): UIFeature<IframeComponentFeatureDescription>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
|
||||||
import { ComponentInterface, DecryptedItemInterface } from '@standardnotes/models'
|
import { ComponentInterface, DecryptedItemInterface } from '@standardnotes/models'
|
||||||
|
|
||||||
import { FeatureStatus } from './FeatureStatus'
|
import { FeatureStatus } from './FeatureStatus'
|
||||||
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
|
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
|
||||||
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface FeaturesClientInterface {
|
export interface FeaturesClientInterface {
|
||||||
initializeFromDisk(): void
|
getFeatureStatus(
|
||||||
getFeatureStatus(featureId: FeatureIdentifier, options?: { inContextOfItem?: DecryptedItemInterface }): FeatureStatus
|
featureId: NativeFeatureIdentifier | Uuid,
|
||||||
|
options?: { inContextOfItem?: DecryptedItemInterface },
|
||||||
|
): FeatureStatus
|
||||||
hasMinimumRole(role: string): boolean
|
hasMinimumRole(role: string): boolean
|
||||||
|
|
||||||
hasFirstPartyOfflineSubscription(): boolean
|
hasFirstPartyOfflineSubscription(): boolean
|
||||||
@@ -16,13 +19,13 @@ export interface FeaturesClientInterface {
|
|||||||
|
|
||||||
isThirdPartyFeature(identifier: string): boolean
|
isThirdPartyFeature(identifier: string): boolean
|
||||||
|
|
||||||
toggleExperimentalFeature(identifier: FeatureIdentifier): void
|
toggleExperimentalFeature(identifier: string): void
|
||||||
getExperimentalFeatures(): FeatureIdentifier[]
|
getExperimentalFeatures(): string[]
|
||||||
getEnabledExperimentalFeatures(): FeatureIdentifier[]
|
getEnabledExperimentalFeatures(): string[]
|
||||||
enableExperimentalFeature(identifier: FeatureIdentifier): void
|
enableExperimentalFeature(identifier: string): void
|
||||||
disableExperimentalFeature(identifier: FeatureIdentifier): void
|
disableExperimentalFeature(identifier: string): void
|
||||||
isExperimentalFeatureEnabled(identifier: FeatureIdentifier): boolean
|
isExperimentalFeatureEnabled(identifier: string): boolean
|
||||||
isExperimentalFeature(identifier: FeatureIdentifier): boolean
|
isExperimentalFeature(identifier: string): boolean
|
||||||
|
|
||||||
downloadRemoteThirdPartyFeature(urlOrCode: string): Promise<ComponentInterface | undefined>
|
downloadRemoteThirdPartyFeature(urlOrCode: string): Promise<ComponentInterface | undefined>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export enum SessionEvent {
|
export enum SessionEvent {
|
||||||
Restored = 'SessionRestored',
|
Restored = 'SessionEvent:SessionRestored',
|
||||||
Revoked = 'SessionRevoked',
|
Revoked = 'SessionEvent:SessionRevoked',
|
||||||
UserKeyPairChanged = 'UserKeyPairChanged',
|
UserKeyPairChanged = 'SessionEvent:UserKeyPairChanged',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ export interface SessionsClientInterface {
|
|||||||
getWorkspaceDisplayIdentifier(): string
|
getWorkspaceDisplayIdentifier(): string
|
||||||
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
|
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
|
||||||
|
|
||||||
initializeFromDisk(): Promise<void>
|
|
||||||
|
|
||||||
getUser(): User | undefined
|
getUser(): User | undefined
|
||||||
isSignedIn(): boolean
|
isSignedIn(): boolean
|
||||||
get userUuid(): string
|
get userUuid(): string
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||||
|
import { SessionEvent } from './../Session/SessionEvent'
|
||||||
|
import { ApplicationEvent } from './../Event/ApplicationEvent'
|
||||||
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
||||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||||
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
|
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
|
||||||
@@ -21,11 +24,66 @@ describe('SubscriptionManager', () => {
|
|||||||
subscriptionApiService.listInvites = jest.fn()
|
subscriptionApiService.listInvites = jest.fn()
|
||||||
|
|
||||||
sessions = {} as jest.Mocked<SessionsClientInterface>
|
sessions = {} as jest.Mocked<SessionsClientInterface>
|
||||||
|
sessions.isSignedIn = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
storage = {} as jest.Mocked<StorageServiceInterface>
|
storage = {} as jest.Mocked<StorageServiceInterface>
|
||||||
|
|
||||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||||
internalEventBus.addEventHandler = jest.fn()
|
internalEventBus.addEventHandler = jest.fn()
|
||||||
|
internalEventBus.publish = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('event handling', () => {
|
||||||
|
it('should fetch subscriptions when the application has launched', async () => {
|
||||||
|
const manager = createManager()
|
||||||
|
jest.spyOn(manager, 'fetchOnlineSubscription')
|
||||||
|
jest.spyOn(manager, 'fetchAvailableSubscriptions')
|
||||||
|
|
||||||
|
await manager.handleEvent({ type: ApplicationEvent.Launched, payload: {} })
|
||||||
|
|
||||||
|
expect(manager.fetchOnlineSubscription).toHaveBeenCalledTimes(1)
|
||||||
|
expect(manager.fetchAvailableSubscriptions).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fetch online subscription when user roles have changed', async () => {
|
||||||
|
const manager = createManager()
|
||||||
|
jest.spyOn(manager, 'fetchOnlineSubscription')
|
||||||
|
|
||||||
|
await manager.handleEvent({ type: ApplicationEvent.UserRolesChanged, payload: {} })
|
||||||
|
|
||||||
|
expect(manager.fetchOnlineSubscription).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fetch online subscription when session is restored', async () => {
|
||||||
|
const manager = createManager()
|
||||||
|
jest.spyOn(manager, 'fetchOnlineSubscription')
|
||||||
|
|
||||||
|
await manager.handleEvent({ type: SessionEvent.Restored, payload: {} })
|
||||||
|
|
||||||
|
expect(manager.fetchOnlineSubscription).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fetch online subscription when user has signed in', async () => {
|
||||||
|
const manager = createManager()
|
||||||
|
jest.spyOn(manager, 'fetchOnlineSubscription')
|
||||||
|
|
||||||
|
await manager.handleEvent({ type: ApplicationEvent.SignedIn, payload: {} })
|
||||||
|
|
||||||
|
expect(manager.fetchOnlineSubscription).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle stage change and notify event', async () => {
|
||||||
|
const manager = createManager()
|
||||||
|
jest.spyOn(manager, 'loadSubscriptionFromStorage')
|
||||||
|
storage.getValue = jest.fn().mockReturnValue({})
|
||||||
|
|
||||||
|
await manager.handleEvent({
|
||||||
|
type: ApplicationEvent.ApplicationStageChanged,
|
||||||
|
payload: { stage: ApplicationStage.StorageDecrypted_09 },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(manager.loadSubscriptionFromStorage).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invite user by email to a shared subscription', async () => {
|
it('should invite user by email to a shared subscription', async () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { SessionEvent } from './../Session/SessionEvent'
|
||||||
import { StorageKey } from './../Storage/StorageKeys'
|
import { StorageKey } from './../Storage/StorageKeys'
|
||||||
import { ApplicationStage } from './../Application/ApplicationStage'
|
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||||
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
||||||
@@ -51,6 +52,7 @@ export class SubscriptionManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ApplicationEvent.UserRolesChanged:
|
case ApplicationEvent.UserRolesChanged:
|
||||||
|
case SessionEvent.Restored:
|
||||||
case ApplicationEvent.SignedIn:
|
case ApplicationEvent.SignedIn:
|
||||||
void this.fetchOnlineSubscription()
|
void this.fetchOnlineSubscription()
|
||||||
break
|
break
|
||||||
@@ -58,13 +60,17 @@ export class SubscriptionManager
|
|||||||
case ApplicationEvent.ApplicationStageChanged: {
|
case ApplicationEvent.ApplicationStageChanged: {
|
||||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||||
if (stage === ApplicationStage.StorageDecrypted_09) {
|
if (stage === ApplicationStage.StorageDecrypted_09) {
|
||||||
this.onlineSubscription = this.storage.getValue(StorageKey.Subscription)
|
this.loadSubscriptionFromStorage()
|
||||||
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSubscriptionFromStorage(): void {
|
||||||
|
this.onlineSubscription = this.storage.getValue(StorageKey.Subscription)
|
||||||
|
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
||||||
|
}
|
||||||
|
|
||||||
hasOnlineSubscription(): boolean {
|
hasOnlineSubscription(): boolean {
|
||||||
return this.onlineSubscription != undefined
|
return this.onlineSubscription != undefined
|
||||||
}
|
}
|
||||||
@@ -204,7 +210,7 @@ export class SubscriptionManager
|
|||||||
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchAvailableSubscriptions(): Promise<void> {
|
async fetchAvailableSubscriptions(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await this.subscriptionApiService.getAvailableSubscriptions()
|
const response = await this.subscriptionApiService.getAvailableSubscriptions()
|
||||||
|
|
||||||
|
|||||||
@@ -442,12 +442,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
|||||||
|
|
||||||
this.sockets.loadWebSocketUrl()
|
this.sockets.loadWebSocketUrl()
|
||||||
|
|
||||||
await this.sessions.initializeFromDisk()
|
|
||||||
|
|
||||||
this.settings.initializeFromDisk()
|
this.settings.initializeFromDisk()
|
||||||
|
|
||||||
this.features.initializeFromDisk()
|
|
||||||
|
|
||||||
this.launched = true
|
this.launched = true
|
||||||
await this.notifyEvent(ApplicationEvent.Launched)
|
await this.notifyEvent(ApplicationEvent.Launched)
|
||||||
await this.handleStage(ApplicationStage.Launched_10)
|
await this.handleStage(ApplicationStage.Launched_10)
|
||||||
@@ -1134,6 +1130,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
|||||||
this.events.addEventHandler(this.dependencies.get(TYPES.SyncService), IntegrityEvent.IntegrityCheckCompleted)
|
this.events.addEventHandler(this.dependencies.get(TYPES.SyncService), IntegrityEvent.IntegrityCheckCompleted)
|
||||||
this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered)
|
this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered)
|
||||||
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed)
|
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed)
|
||||||
|
this.events.addEventHandler(this.dependencies.get(TYPES.SubscriptionManager), SessionEvent.Restored)
|
||||||
|
|
||||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
||||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
||||||
@@ -1165,6 +1162,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApplicationEvent.ApplicationStageChanged)
|
||||||
this.events.addEventHandler(
|
this.events.addEventHandler(
|
||||||
this.dependencies.get(TYPES.SelfContactManager),
|
this.dependencies.get(TYPES.SelfContactManager),
|
||||||
ApplicationEvent.ApplicationStageChanged,
|
ApplicationEvent.ApplicationStageChanged,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ApplicationStage } from '@standardnotes/services'
|
import { ApplicationStage } from '@standardnotes/services'
|
||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
|
||||||
import { Migration } from '@Lib/Migrations/Migration'
|
import { Migration } from '@Lib/Migrations/Migration'
|
||||||
import { ThemeInterface } from '@standardnotes/models'
|
import { ThemeInterface } from '@standardnotes/models'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
const NoDistractionIdentifier = 'org.standardnotes.theme-no-distraction' as FeatureIdentifier
|
const NoDistractionIdentifier = 'org.standardnotes.theme-no-distraction'
|
||||||
|
|
||||||
export class Migration2_42_0 extends Migration {
|
export class Migration2_42_0 extends Migration {
|
||||||
static override version(): string {
|
static override version(): string {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
|
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType, Uuid } from '@standardnotes/domain-core'
|
||||||
import {
|
import {
|
||||||
ActionObserver,
|
ActionObserver,
|
||||||
PayloadEmitSource,
|
PayloadEmitSource,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
UIFeature,
|
UIFeature,
|
||||||
ComponentInterface,
|
ComponentInterface,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
ThemeInterface,
|
|
||||||
ComponentPreferencesEntry,
|
ComponentPreferencesEntry,
|
||||||
AllComponentPreferences,
|
AllComponentPreferences,
|
||||||
SNNote,
|
SNNote,
|
||||||
@@ -21,15 +20,14 @@ import {
|
|||||||
import {
|
import {
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
FeatureIdentifier,
|
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
FindNativeTheme,
|
FindNativeTheme,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
ComponentFeatureDescription,
|
ComponentFeatureDescription,
|
||||||
ThemeFeatureDescription,
|
ThemeFeatureDescription,
|
||||||
EditorIdentifier,
|
|
||||||
GetIframeEditors,
|
GetIframeEditors,
|
||||||
GetNativeThemes,
|
GetNativeThemes,
|
||||||
|
NativeFeatureIdentifier,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { Copy, removeFromArray, sleep, isNotUndefined } from '@standardnotes/utils'
|
import { Copy, removeFromArray, sleep, isNotUndefined } from '@standardnotes/utils'
|
||||||
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
||||||
@@ -287,7 +285,7 @@ export class SNComponentManager
|
|||||||
const url = this.urlForFeature(feature)
|
const url = this.urlForFeature(feature)
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
this.device.registerComponentUrl(feature.uniqueIdentifier, url)
|
this.device.registerComponentUrl(feature.uniqueIdentifier.value, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +370,7 @@ export class SNComponentManager
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureStatus = this.features.getFeatureStatus(uiFeature.featureIdentifier)
|
const featureStatus = this.features.getFeatureStatus(uiFeature.uniqueIdentifier)
|
||||||
if (featureStatus !== FeatureStatus.Entitled) {
|
if (featureStatus !== FeatureStatus.Entitled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -398,28 +396,50 @@ export class SNComponentManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getActiveThemes(): UIFeature<ThemeFeatureDescription>[] {
|
public getActiveThemes(): UIFeature<ThemeFeatureDescription>[] {
|
||||||
const activeThemesIdentifiers = this.getActiveThemesIdentifiers()
|
const { features, uuids } = this.getActiveThemesIdentifiers()
|
||||||
|
|
||||||
const thirdPartyThemes = this.items.findItems<ThemeInterface>(activeThemesIdentifiers).map((item) => {
|
const thirdPartyThemes = uuids
|
||||||
return new UIFeature<ThemeFeatureDescription>(item)
|
.map((uuid) => {
|
||||||
})
|
const component = this.items.findItem<ComponentInterface>(uuid.value)
|
||||||
|
if (component) {
|
||||||
|
return new UIFeature<ThemeFeatureDescription>(component)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
.filter(isNotUndefined)
|
||||||
|
|
||||||
const nativeThemes = activeThemesIdentifiers
|
const nativeThemes = features
|
||||||
.map((identifier) => {
|
.map((identifier) => {
|
||||||
return FindNativeTheme(identifier as FeatureIdentifier)
|
return FindNativeTheme(identifier.value)
|
||||||
})
|
})
|
||||||
.filter(isNotUndefined)
|
.filter(isNotUndefined)
|
||||||
.map((theme) => new UIFeature(theme))
|
.map((theme) => new UIFeature(theme))
|
||||||
|
|
||||||
const entitledThemes = [...thirdPartyThemes, ...nativeThemes].filter((theme) => {
|
const entitledThemes = [...thirdPartyThemes, ...nativeThemes].filter((theme) => {
|
||||||
return this.features.getFeatureStatus(theme.featureIdentifier) === FeatureStatus.Entitled
|
return this.features.getFeatureStatus(theme.uniqueIdentifier) === FeatureStatus.Entitled
|
||||||
})
|
})
|
||||||
|
|
||||||
return entitledThemes
|
return entitledThemes
|
||||||
}
|
}
|
||||||
|
|
||||||
public getActiveThemesIdentifiers(): string[] {
|
public getActiveThemesIdentifiers(): { features: NativeFeatureIdentifier[]; uuids: Uuid[] } {
|
||||||
return this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
const features: NativeFeatureIdentifier[] = []
|
||||||
|
const uuids: Uuid[] = []
|
||||||
|
|
||||||
|
const strings = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||||
|
for (const string of strings) {
|
||||||
|
const nativeIdentifier = NativeFeatureIdentifier.create(string)
|
||||||
|
if (!nativeIdentifier.isFailed()) {
|
||||||
|
features.push(nativeIdentifier.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = Uuid.create(string)
|
||||||
|
if (!uuid.isFailed()) {
|
||||||
|
uuids.push(uuid.getValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { features, uuids }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async toggleComponent(component: ComponentInterface): Promise<void> {
|
public async toggleComponent(component: ComponentInterface): Promise<void> {
|
||||||
@@ -437,7 +457,7 @@ export class SNComponentManager
|
|||||||
return usecase.execute(note)
|
return usecase.execute(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultEditorIdentifier(currentTag?: SNTag): EditorIdentifier {
|
getDefaultEditorIdentifier(currentTag?: SNTag): string {
|
||||||
const usecase = new GetDefaultEditorIdentifier(this.preferences, this.items)
|
const usecase = new GetDefaultEditorIdentifier(this.preferences, this.items)
|
||||||
return usecase.execute(currentTag).getValue()
|
return usecase.execute(currentTag).getValue()
|
||||||
}
|
}
|
||||||
@@ -468,7 +488,7 @@ export class SNComponentManager
|
|||||||
this.preferences.getValue(PrefKey.ComponentPreferences, undefined) ?? {},
|
this.preferences.getValue(PrefKey.ComponentPreferences, undefined) ?? {},
|
||||||
)
|
)
|
||||||
|
|
||||||
const preferencesLookupKey = uiFeature.uniqueIdentifier
|
const preferencesLookupKey = uiFeature.uniqueIdentifier.value
|
||||||
|
|
||||||
mutablePreferencesValue[preferencesLookupKey] = preferences
|
mutablePreferencesValue[preferencesLookupKey] = preferences
|
||||||
|
|
||||||
@@ -482,7 +502,7 @@ export class SNComponentManager
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferencesLookupKey = component.uniqueIdentifier
|
const preferencesLookupKey = component.uniqueIdentifier.value
|
||||||
|
|
||||||
return preferences[preferencesLookupKey]
|
return preferences[preferencesLookupKey]
|
||||||
}
|
}
|
||||||
@@ -490,31 +510,31 @@ export class SNComponentManager
|
|||||||
async addActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
async addActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||||
const activeThemes = (this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []).slice()
|
const activeThemes = (this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []).slice()
|
||||||
|
|
||||||
activeThemes.push(theme.uniqueIdentifier)
|
activeThemes.push(theme.uniqueIdentifier.value)
|
||||||
|
|
||||||
await this.preferences.setValue(PrefKey.ActiveThemes, activeThemes)
|
await this.preferences.setValue(PrefKey.ActiveThemes, activeThemes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async replaceActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
async replaceActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||||
await this.preferences.setValue(PrefKey.ActiveThemes, [theme.uniqueIdentifier])
|
await this.preferences.setValue(PrefKey.ActiveThemes, [theme.uniqueIdentifier.value])
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
async removeActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||||
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||||
|
|
||||||
const filteredThemes = activeThemes.filter((activeTheme) => activeTheme !== theme.uniqueIdentifier)
|
const filteredThemes = activeThemes.filter((activeTheme) => activeTheme !== theme.uniqueIdentifier.value)
|
||||||
|
|
||||||
await this.preferences.setValue(PrefKey.ActiveThemes, filteredThemes)
|
await this.preferences.setValue(PrefKey.ActiveThemes, filteredThemes)
|
||||||
}
|
}
|
||||||
|
|
||||||
isThemeActive(theme: UIFeature<ThemeFeatureDescription>): boolean {
|
isThemeActive(theme: UIFeature<ThemeFeatureDescription>): boolean {
|
||||||
if (this.features.getFeatureStatus(theme.featureIdentifier) !== FeatureStatus.Entitled) {
|
if (this.features.getFeatureStatus(theme.uniqueIdentifier) !== FeatureStatus.Entitled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||||
|
|
||||||
return activeThemes.includes(theme.uniqueIdentifier)
|
return activeThemes.includes(theme.uniqueIdentifier.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
async addActiveComponent(component: ComponentInterface): Promise<void> {
|
async addActiveComponent(component: ComponentInterface): Promise<void> {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import {
|
|||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
|
NativeFeatureIdentifier,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import {
|
import {
|
||||||
isString,
|
isString,
|
||||||
@@ -72,7 +73,7 @@ import {
|
|||||||
isNotUndefined,
|
isNotUndefined,
|
||||||
uniqueArray,
|
uniqueArray,
|
||||||
} from '@standardnotes/utils'
|
} from '@standardnotes/utils'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export class ComponentViewer implements ComponentViewerInterface {
|
export class ComponentViewer implements ComponentViewerInterface {
|
||||||
private streamItems?: string[]
|
private streamItems?: string[]
|
||||||
@@ -214,12 +215,12 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
this.readonly = readonly
|
this.readonly = readonly
|
||||||
}
|
}
|
||||||
|
|
||||||
get componentUniqueIdentifier(): string {
|
get componentUniqueIdentifier(): NativeFeatureIdentifier | Uuid {
|
||||||
return this.componentOrFeature.uniqueIdentifier
|
return this.componentOrFeature.uniqueIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFeatureStatus(): FeatureStatus {
|
public getFeatureStatus(): FeatureStatus {
|
||||||
return this.services.features.getFeatureStatus(this.componentOrFeature.featureIdentifier, {
|
return this.services.features.getFeatureStatus(this.componentUniqueIdentifier, {
|
||||||
inContextOfItem: this.getContextItem(),
|
inContextOfItem: this.getContextItem(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,9 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedComponent = items.find((item) => item.uuid === this.componentUniqueIdentifier) as ComponentInterface
|
const updatedComponent = items.find(
|
||||||
|
(item) => item.uuid === this.componentUniqueIdentifier.value,
|
||||||
|
) as ComponentInterface
|
||||||
if (!updatedComponent) {
|
if (!updatedComponent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -289,7 +292,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
|
|
||||||
this.updateOurComponentRefFromChangedItems(nondeletedItems)
|
this.updateOurComponentRefFromChangedItems(nondeletedItems)
|
||||||
|
|
||||||
const areWeOriginator = sourceKey && sourceKey === this.componentUniqueIdentifier
|
const areWeOriginator = sourceKey && sourceKey === this.componentUniqueIdentifier.value
|
||||||
if (areWeOriginator) {
|
if (areWeOriginator) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -326,7 +329,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
]
|
]
|
||||||
|
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
() => {
|
() => {
|
||||||
this.sendItemsInReply(items, this.streamItemsOriginalMessage as ComponentMessage)
|
this.sendItemsInReply(items, this.streamItemsOriginalMessage as ComponentMessage)
|
||||||
@@ -341,7 +344,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
},
|
},
|
||||||
] as ComponentPermission[]
|
] as ComponentPermission[]
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredContextPermissions,
|
requiredContextPermissions,
|
||||||
() => {
|
() => {
|
||||||
this.log(
|
this.log(
|
||||||
@@ -414,7 +417,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
|
|
||||||
private getClientData(item: DecryptedItemInterface): Record<string, unknown> {
|
private getClientData(item: DecryptedItemInterface): Record<string, unknown> {
|
||||||
const globalComponentData = item.getDomainData(ComponentDataDomain) || {}
|
const globalComponentData = item.getDomainData(ComponentDataDomain) || {}
|
||||||
const thisComponentData = globalComponentData[this.componentUniqueIdentifier] || {}
|
const thisComponentData = globalComponentData[this.componentUniqueIdentifier.value] || {}
|
||||||
return thisComponentData as Record<string, unknown>
|
return thisComponentData as Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +533,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
sessionKey: this.sessionKey,
|
sessionKey: this.sessionKey,
|
||||||
componentData: componentData,
|
componentData: componentData,
|
||||||
data: {
|
data: {
|
||||||
uuid: this.componentUniqueIdentifier,
|
uuid: this.componentUniqueIdentifier.value,
|
||||||
environment: environmentToString(this.config.environment),
|
environment: environmentToString(this.config.environment),
|
||||||
platform: platformToString(this.config.platform),
|
platform: platformToString(this.config.platform),
|
||||||
activeThemeUrls: this.config.componentManagerFunctions.urlsForActiveThemes(),
|
activeThemeUrls: this.config.componentManagerFunctions.urlsForActiveThemes(),
|
||||||
@@ -602,7 +605,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
() => {
|
() => {
|
||||||
if (!this.streamItems) {
|
if (!this.streamItems) {
|
||||||
@@ -627,7 +630,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
]
|
]
|
||||||
|
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
() => {
|
() => {
|
||||||
if (!this.streamContextItemOriginalMessage) {
|
if (!this.streamContextItemOriginalMessage) {
|
||||||
@@ -684,7 +687,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
|
|
||||||
async () => {
|
async () => {
|
||||||
@@ -766,13 +769,13 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
const allComponentData = Copy<Record<string, unknown>>(
|
const allComponentData = Copy<Record<string, unknown>>(
|
||||||
mutator.getItem().getDomainData(ComponentDataDomain) || {},
|
mutator.getItem().getDomainData(ComponentDataDomain) || {},
|
||||||
)
|
)
|
||||||
allComponentData[this.componentUniqueIdentifier] = responseItem.clientData
|
allComponentData[this.componentUniqueIdentifier.value] = responseItem.clientData
|
||||||
mutator.setDomainData(allComponentData, ComponentDataDomain)
|
mutator.setDomainData(allComponentData, ComponentDataDomain)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MutationType.UpdateUserTimestamps,
|
MutationType.UpdateUserTimestamps,
|
||||||
PayloadEmitSource.ComponentRetrieved,
|
PayloadEmitSource.ComponentRetrieved,
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
this.services.sync
|
this.services.sync
|
||||||
@@ -807,7 +810,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
]
|
]
|
||||||
|
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
async () => {
|
async () => {
|
||||||
responseItems = this.responseItemsByRemovingPrivateProperties(responseItems)
|
responseItems = this.responseItemsByRemovingPrivateProperties(responseItems)
|
||||||
@@ -834,13 +837,13 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
const allComponentClientData = Copy<Record<string, unknown>>(
|
const allComponentClientData = Copy<Record<string, unknown>>(
|
||||||
item.getDomainData(ComponentDataDomain) || {},
|
item.getDomainData(ComponentDataDomain) || {},
|
||||||
)
|
)
|
||||||
allComponentClientData[this.componentUniqueIdentifier] = responseItem.clientData
|
allComponentClientData[this.componentUniqueIdentifier.value] = responseItem.clientData
|
||||||
mutator.setDomainData(allComponentClientData, ComponentDataDomain)
|
mutator.setDomainData(allComponentClientData, ComponentDataDomain)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MutationType.UpdateUserTimestamps,
|
MutationType.UpdateUserTimestamps,
|
||||||
PayloadEmitSource.ComponentCreated,
|
PayloadEmitSource.ComponentCreated,
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
)
|
)
|
||||||
processedItems.push(item)
|
processedItems.push(item)
|
||||||
}
|
}
|
||||||
@@ -874,7 +877,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
]
|
]
|
||||||
|
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
async () => {
|
async () => {
|
||||||
const itemsData = items
|
const itemsData = items
|
||||||
@@ -911,7 +914,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
|||||||
private handleSetComponentPreferencesMessage(message: ComponentMessage): void {
|
private handleSetComponentPreferencesMessage(message: ComponentMessage): void {
|
||||||
const noPermissionsRequired: ComponentPermission[] = []
|
const noPermissionsRequired: ComponentPermission[] = []
|
||||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||||
this.componentUniqueIdentifier,
|
this.componentUniqueIdentifier.value,
|
||||||
noPermissionsRequired,
|
noPermissionsRequired,
|
||||||
async () => {
|
async () => {
|
||||||
const newPreferences = <ComponentPreferencesEntry | undefined>message.data.componentData
|
const newPreferences = <ComponentPreferencesEntry | undefined>message.data.componentData
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComponentArea, ComponentAction, FeatureIdentifier, LegacyFileSafeIdentifier } from '@standardnotes/features'
|
import { ComponentArea, ComponentAction, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { ComponentMessage, MessageData, OutgoingItemMessagePayload } from '@standardnotes/models'
|
import { ComponentMessage, MessageData, OutgoingItemMessagePayload } from '@standardnotes/models'
|
||||||
import { UuidString } from '@Lib/Types/UuidString'
|
import { UuidString } from '@Lib/Types/UuidString'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
@@ -17,9 +17,9 @@ export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
|
|||||||
* Extensions allowed to batch stream AllowedBatchContentTypes
|
* Extensions allowed to batch stream AllowedBatchContentTypes
|
||||||
*/
|
*/
|
||||||
export const AllowedBatchStreaming = Object.freeze([
|
export const AllowedBatchStreaming = Object.freeze([
|
||||||
LegacyFileSafeIdentifier,
|
NativeFeatureIdentifier.TYPES.LegacyFileSafeIdentifier,
|
||||||
FeatureIdentifier.DeprecatedFileSafe,
|
NativeFeatureIdentifier.TYPES.DeprecatedFileSafe,
|
||||||
FeatureIdentifier.DeprecatedBoldEditor,
|
NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { DoesEditorChangeRequireAlertUseCase } from './DoesEditorChangeRequireAlert'
|
import { DoesEditorChangeRequireAlertUseCase } from './DoesEditorChangeRequireAlert'
|
||||||
import { UIFeature } from '@standardnotes/models'
|
import { UIFeature } from '@standardnotes/models'
|
||||||
|
|
||||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,57 +19,77 @@ describe('editor change alert', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not require alert switching from plain editor', () => {
|
it('should not require alert switching from plain editor', () => {
|
||||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
|
)!
|
||||||
const requiresAlert = usecase.execute(undefined, component)
|
const requiresAlert = usecase.execute(undefined, component)
|
||||||
expect(requiresAlert).toBe(false)
|
expect(requiresAlert).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not require alert switching to plain editor', () => {
|
it('should not require alert switching to plain editor', () => {
|
||||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
|
)!
|
||||||
const requiresAlert = usecase.execute(component, undefined)
|
const requiresAlert = usecase.execute(component, undefined)
|
||||||
expect(requiresAlert).toBe(false)
|
expect(requiresAlert).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not require alert switching from a markdown editor', () => {
|
it('should not require alert switching from a markdown editor', () => {
|
||||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
|
)!
|
||||||
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
FeatureIdentifier.MarkdownProEditor,
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
)
|
)
|
||||||
const requiresAlert = usecase.execute(markdownEditor, htmlEditor)
|
const requiresAlert = usecase.execute(markdownEditor, htmlEditor)
|
||||||
expect(requiresAlert).toBe(false)
|
expect(requiresAlert).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not require alert switching to a markdown editor', () => {
|
it('should not require alert switching to a markdown editor', () => {
|
||||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
|
)!
|
||||||
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
FeatureIdentifier.MarkdownProEditor,
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
)
|
)
|
||||||
const requiresAlert = usecase.execute(htmlEditor, markdownEditor)
|
const requiresAlert = usecase.execute(htmlEditor, markdownEditor)
|
||||||
expect(requiresAlert).toBe(false)
|
expect(requiresAlert).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not require alert switching from & to a html editor', () => {
|
it('should not require alert switching from & to a html editor', () => {
|
||||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
|
)!
|
||||||
const requiresAlert = usecase.execute(htmlEditor, htmlEditor)
|
const requiresAlert = usecase.execute(htmlEditor, htmlEditor)
|
||||||
expect(requiresAlert).toBe(false)
|
expect(requiresAlert).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should require alert switching from a html editor to custom editor', () => {
|
it('should require alert switching from a html editor to custom editor', () => {
|
||||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
|
)!
|
||||||
|
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||||
|
)
|
||||||
const requiresAlert = usecase.execute(htmlEditor, customEditor)
|
const requiresAlert = usecase.execute(htmlEditor, customEditor)
|
||||||
expect(requiresAlert).toBe(true)
|
expect(requiresAlert).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should require alert switching from a custom editor to html editor', () => {
|
it('should require alert switching from a custom editor to html editor', () => {
|
||||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||||
|
)!
|
||||||
|
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||||
|
)
|
||||||
const requiresAlert = usecase.execute(customEditor, htmlEditor)
|
const requiresAlert = usecase.execute(customEditor, htmlEditor)
|
||||||
expect(requiresAlert).toBe(true)
|
expect(requiresAlert).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should require alert switching from a custom editor to custom editor', () => {
|
it('should require alert switching from a custom editor to custom editor', () => {
|
||||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||||
|
)
|
||||||
const requiresAlert = usecase.execute(customEditor, customEditor)
|
const requiresAlert = usecase.execute(customEditor, customEditor)
|
||||||
expect(requiresAlert).toBe(true)
|
expect(requiresAlert).toBe(true)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createNote } from '@Lib/Spec/SpecUtils'
|
import { createNote } from '@Lib/Spec/SpecUtils'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { EditorForNoteUseCase } from './EditorForNote'
|
import { EditorForNoteUseCase } from './EditorForNote'
|
||||||
import { ItemManagerInterface } from '@standardnotes/services'
|
import { ItemManagerInterface } from '@standardnotes/services'
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ describe('EditorForNote', () => {
|
|||||||
noteType: NoteType.Plain,
|
noteType: NoteType.Plain,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(usecase.execute(note).featureIdentifier).toBe(FeatureIdentifier.PlainEditor)
|
expect(usecase.execute(note).featureIdentifier).toBe(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('getEditorForNote should call legacy function if no note editorIdentifier or noteType', () => {
|
it('getEditorForNote should call legacy function if no note editorIdentifier or noteType', () => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
FeatureIdentifier,
|
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
GetIframeAndNativeEditors,
|
GetIframeAndNativeEditors,
|
||||||
GetPlainNoteFeature,
|
GetPlainNoteFeature,
|
||||||
@@ -47,11 +46,9 @@ export class EditorForNoteUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private componentOrNativeFeatureForIdentifier(
|
private componentOrNativeFeatureForIdentifier(
|
||||||
identifier: FeatureIdentifier | string,
|
identifier: string,
|
||||||
): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription> | undefined {
|
): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription> | undefined {
|
||||||
const nativeFeature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(
|
const nativeFeature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(identifier)
|
||||||
identifier as FeatureIdentifier,
|
|
||||||
)
|
|
||||||
if (nativeFeature) {
|
if (nativeFeature) {
|
||||||
return new UIFeature(nativeFeature)
|
return new UIFeature(nativeFeature)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
||||||
import { GetDefaultEditorIdentifier } from './GetDefaultEditorIdentifier'
|
import { GetDefaultEditorIdentifier } from './GetDefaultEditorIdentifier'
|
||||||
import { ComponentArea, FeatureIdentifier } from '@standardnotes/features'
|
import { ComponentArea, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { SNComponent, SNTag } from '@standardnotes/models'
|
import { SNComponent, SNTag } from '@standardnotes/models'
|
||||||
|
|
||||||
describe('getDefaultEditorIdentifier', () => {
|
describe('getDefaultEditorIdentifier', () => {
|
||||||
@@ -21,33 +21,33 @@ describe('getDefaultEditorIdentifier', () => {
|
|||||||
it('should return plain editor if no default tag editor or component editor', () => {
|
it('should return plain editor if no default tag editor or component editor', () => {
|
||||||
const editorIdentifier = usecase.execute().getValue()
|
const editorIdentifier = usecase.execute().getValue()
|
||||||
|
|
||||||
expect(editorIdentifier).toEqual(FeatureIdentifier.PlainEditor)
|
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return pref key based value if available', () => {
|
it('should return pref key based value if available', () => {
|
||||||
preferences.getValue = jest.fn().mockReturnValue(FeatureIdentifier.SuperEditor)
|
preferences.getValue = jest.fn().mockReturnValue(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||||
|
|
||||||
const editorIdentifier = usecase.execute().getValue()
|
const editorIdentifier = usecase.execute().getValue()
|
||||||
|
|
||||||
expect(editorIdentifier).toEqual(FeatureIdentifier.SuperEditor)
|
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return default tag identifier if tag supplied', () => {
|
it('should return default tag identifier if tag supplied', () => {
|
||||||
const tag = {
|
const tag = {
|
||||||
preferences: {
|
preferences: {
|
||||||
editorIdentifier: FeatureIdentifier.SuperEditor,
|
editorIdentifier: NativeFeatureIdentifier.TYPES.SuperEditor,
|
||||||
},
|
},
|
||||||
} as jest.Mocked<SNTag>
|
} as jest.Mocked<SNTag>
|
||||||
|
|
||||||
const editorIdentifier = usecase.execute(tag).getValue()
|
const editorIdentifier = usecase.execute(tag).getValue()
|
||||||
|
|
||||||
expect(editorIdentifier).toEqual(FeatureIdentifier.SuperEditor)
|
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return legacy editor identifier', () => {
|
it('should return legacy editor identifier', () => {
|
||||||
const editor = {
|
const editor = {
|
||||||
legacyIsDefaultEditor: jest.fn().mockReturnValue(true),
|
legacyIsDefaultEditor: jest.fn().mockReturnValue(true),
|
||||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
area: ComponentArea.Editor,
|
area: ComponentArea.Editor,
|
||||||
} as unknown as jest.Mocked<SNComponent>
|
} as unknown as jest.Mocked<SNComponent>
|
||||||
|
|
||||||
@@ -55,6 +55,6 @@ describe('getDefaultEditorIdentifier', () => {
|
|||||||
|
|
||||||
const editorIdentifier = usecase.execute().getValue()
|
const editorIdentifier = usecase.execute().getValue()
|
||||||
|
|
||||||
expect(editorIdentifier).toEqual(FeatureIdentifier.MarkdownProEditor)
|
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.MarkdownProEditor)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||||
import { ComponentArea, EditorIdentifier, FeatureIdentifier } from '@standardnotes/features'
|
import { ComponentArea, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { ComponentInterface, PrefKey, SNTag } from '@standardnotes/models'
|
import { ComponentInterface, PrefKey, SNTag } from '@standardnotes/models'
|
||||||
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
||||||
|
|
||||||
export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<EditorIdentifier> {
|
export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<string> {
|
||||||
constructor(private preferences: PreferenceServiceInterface, private items: ItemManagerInterface) {}
|
constructor(private preferences: PreferenceServiceInterface, private items: ItemManagerInterface) {}
|
||||||
|
|
||||||
execute(currentTag?: SNTag): Result<EditorIdentifier> {
|
execute(currentTag?: SNTag): Result<string> {
|
||||||
if (currentTag) {
|
if (currentTag) {
|
||||||
const editorIdentifier = currentTag?.preferences?.editorIdentifier
|
const editorIdentifier = currentTag?.preferences?.editorIdentifier
|
||||||
if (editorIdentifier) {
|
if (editorIdentifier) {
|
||||||
@@ -25,7 +25,7 @@ export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<EditorId
|
|||||||
return Result.ok(matchingEditor.identifier)
|
return Result.ok(matchingEditor.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(FeatureIdentifier.PlainEditor)
|
return Result.ok(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
thirdPartyComponentsForArea(area: ComponentArea): ComponentInterface[] {
|
thirdPartyComponentsForArea(area: ComponentArea): ComponentInterface[] {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
import {
|
import {
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
@@ -21,7 +21,7 @@ import { GetFeatureUrl } from './GetFeatureUrl'
|
|||||||
|
|
||||||
const desktopExtHost = 'http://localhost:123'
|
const desktopExtHost = 'http://localhost:123'
|
||||||
|
|
||||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ const thirdPartyFeature = () => {
|
|||||||
local_url: 'sn://Extensions/non-native-identifier/dist/index.html',
|
local_url: 'sn://Extensions/non-native-identifier/dist/index.html',
|
||||||
hosted_url: 'https://example.com/component',
|
hosted_url: 'https://example.com/component',
|
||||||
package_info: {
|
package_info: {
|
||||||
identifier: 'non-native-identifier' as FeatureIdentifier,
|
identifier: 'non-native-identifier',
|
||||||
expires_at: new Date().getTime(),
|
expires_at: new Date().getTime(),
|
||||||
availableInRoles: [],
|
availableInRoles: [],
|
||||||
} as unknown as jest.Mocked<ComponentPackageInfo>,
|
} as unknown as jest.Mocked<ComponentPackageInfo>,
|
||||||
@@ -75,7 +75,9 @@ describe('GetFeatureUrl', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns native path for native component', () => {
|
it('returns native path for native component', () => {
|
||||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
|
)!
|
||||||
const url = usecase.execute(feature)
|
const url = usecase.execute(feature)
|
||||||
expect(url).toEqual(
|
expect(url).toEqual(
|
||||||
`${desktopExtHost}/components/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
`${desktopExtHost}/components/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
||||||
@@ -84,7 +86,7 @@ describe('GetFeatureUrl', () => {
|
|||||||
|
|
||||||
it('returns native path for deprecated native component', () => {
|
it('returns native path for deprecated native component', () => {
|
||||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
FeatureIdentifier.DeprecatedBoldEditor,
|
NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||||
)!
|
)!
|
||||||
const url = usecase.execute(feature)
|
const url = usecase.execute(feature)
|
||||||
expect(url).toEqual(
|
expect(url).toEqual(
|
||||||
@@ -122,7 +124,9 @@ describe('GetFeatureUrl', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns native path for native feature', () => {
|
it('returns native path for native feature', () => {
|
||||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)
|
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||||
|
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
|
)
|
||||||
const url = usecase.execute(feature)
|
const url = usecase.execute(feature)
|
||||||
expect(url).toEqual(
|
expect(url).toEqual(
|
||||||
`http://localhost/components/assets/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
`http://localhost/components/assets/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ContentType } from '@standardnotes/domain-core'
|
|||||||
import {
|
import {
|
||||||
ComponentAction,
|
ComponentAction,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
UIFeatureDescriptionTypes,
|
UIFeatureDescriptionTypes,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
SyncServiceInterface,
|
SyncServiceInterface,
|
||||||
} from '@standardnotes/services'
|
} from '@standardnotes/services'
|
||||||
|
|
||||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(true)
|
).toEqual(true)
|
||||||
@@ -59,7 +59,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(false)
|
).toEqual(false)
|
||||||
@@ -75,7 +75,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(false)
|
).toEqual(false)
|
||||||
@@ -91,7 +91,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(false)
|
).toEqual(false)
|
||||||
@@ -107,7 +107,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedFileSafe),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(false)
|
).toEqual(false)
|
||||||
@@ -127,7 +127,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedFileSafe),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(true)
|
).toEqual(true)
|
||||||
@@ -147,7 +147,7 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(
|
usecase.areRequestedPermissionsValid(
|
||||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedBoldEditor),
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor),
|
||||||
permissions,
|
permissions,
|
||||||
),
|
),
|
||||||
).toEqual(true)
|
).toEqual(true)
|
||||||
@@ -166,7 +166,10 @@ describe('RunWithPermissionsUseCase', () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.areRequestedPermissionsValid(nativeFeatureAsUIFeature(FeatureIdentifier.PlusEditor), permissions),
|
usecase.areRequestedPermissionsValid(
|
||||||
|
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.PlusEditor),
|
||||||
|
permissions,
|
||||||
|
),
|
||||||
).toEqual(false)
|
).toEqual(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
ComponentAction,
|
ComponentAction,
|
||||||
ComponentFeatureDescription,
|
ComponentFeatureDescription,
|
||||||
ComponentPermission,
|
ComponentPermission,
|
||||||
FeatureIdentifier,
|
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { ComponentInterface, ComponentMutator, PermissionDialog, UIFeature } from '@standardnotes/models'
|
import { ComponentInterface, ComponentMutator, PermissionDialog, UIFeature } from '@standardnotes/models'
|
||||||
@@ -228,7 +227,7 @@ export class RunWithPermissionsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private findUIFeature(identifier: string): UIFeature<ComponentFeatureDescription> | undefined {
|
private findUIFeature(identifier: string): UIFeature<ComponentFeatureDescription> | undefined {
|
||||||
const nativeFeature = FindNativeFeature<ComponentFeatureDescription>(identifier as FeatureIdentifier)
|
const nativeFeature = FindNativeFeature<ComponentFeatureDescription>(identifier)
|
||||||
if (nativeFeature) {
|
if (nativeFeature) {
|
||||||
return new UIFeature(nativeFeature)
|
return new UIFeature(nativeFeature)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { ItemInterface, SNFeatureRepo } from '@standardnotes/models'
|
|||||||
import { SyncService } from '../Sync/SyncService'
|
import { SyncService } from '../Sync/SyncService'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/settings'
|
||||||
import { FeaturesService } from '@Lib/Services/Features'
|
import { FeaturesService } from '@Lib/Services/Features'
|
||||||
import { RoleName, ContentType } from '@standardnotes/domain-core'
|
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||||
import { FeatureIdentifier, GetFeatures } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, GetFeatures } from '@standardnotes/features'
|
||||||
import { WebSocketsService } from '../Api/WebsocketsService'
|
import { WebSocketsService } from '../Api/WebsocketsService'
|
||||||
import { SettingsService } from '../Settings'
|
import { SettingsService } from '../Settings'
|
||||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||||
@@ -44,6 +44,7 @@ describe('FeaturesService', () => {
|
|||||||
let roles: string[]
|
let roles: string[]
|
||||||
let items: ItemInterface[]
|
let items: ItemInterface[]
|
||||||
let internalEventBus: InternalEventBusInterface
|
let internalEventBus: InternalEventBusInterface
|
||||||
|
let featureService: FeaturesService
|
||||||
|
|
||||||
const createService = () => {
|
const createService = () => {
|
||||||
return new FeaturesService(
|
return new FeaturesService(
|
||||||
@@ -118,23 +119,81 @@ describe('FeaturesService', () => {
|
|||||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||||
internalEventBus.publish = jest.fn()
|
internalEventBus.publish = jest.fn()
|
||||||
internalEventBus.addEventHandler = jest.fn()
|
internalEventBus.addEventHandler = jest.fn()
|
||||||
|
|
||||||
|
featureService = new FeaturesService(
|
||||||
|
storageService,
|
||||||
|
itemManager,
|
||||||
|
mutator,
|
||||||
|
subscriptions,
|
||||||
|
apiService,
|
||||||
|
webSocketsService,
|
||||||
|
settingsService,
|
||||||
|
userService,
|
||||||
|
syncService,
|
||||||
|
alertService,
|
||||||
|
sessionManager,
|
||||||
|
crypto,
|
||||||
|
internalEventBus,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('experimental features', () => {
|
describe('experimental features', () => {
|
||||||
it('enables/disables an experimental feature', async () => {
|
it('enables/disables an experimental feature', async () => {
|
||||||
storageService.getValue = jest.fn().mockReturnValue(GetFeatures())
|
storageService.getValue = jest.fn().mockReturnValue(GetFeatures())
|
||||||
|
|
||||||
const featuresService = createService()
|
featureService.getExperimentalFeatures = jest.fn().mockReturnValue([NativeFeatureIdentifier.TYPES.PlusEditor])
|
||||||
featuresService.getExperimentalFeatures = jest.fn().mockReturnValue([FeatureIdentifier.PlusEditor])
|
featureService.initializeFromDisk()
|
||||||
featuresService.initializeFromDisk()
|
|
||||||
|
|
||||||
featuresService.enableExperimentalFeature(FeatureIdentifier.PlusEditor)
|
featureService.enableExperimentalFeature(NativeFeatureIdentifier.TYPES.PlusEditor)
|
||||||
|
|
||||||
expect(featuresService.isExperimentalFeatureEnabled(FeatureIdentifier.PlusEditor)).toEqual(true)
|
expect(featureService.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.PlusEditor)).toEqual(true)
|
||||||
|
|
||||||
featuresService.disableExperimentalFeature(FeatureIdentifier.PlusEditor)
|
featureService.disableExperimentalFeature(NativeFeatureIdentifier.TYPES.PlusEditor)
|
||||||
|
|
||||||
expect(featuresService.isExperimentalFeatureEnabled(FeatureIdentifier.PlusEditor)).toEqual(false)
|
expect(featureService.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.PlusEditor)).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('hasFirstPartyOnlineSubscription', () => {
|
||||||
|
it('should be true if signed into first party server and has online subscription', () => {
|
||||||
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not be true if not signed into first party server', () => {
|
||||||
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||||
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not be true if no online subscription', () => {
|
||||||
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
|
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('hasPaidAnyPartyOnlineOrOfflineSubscription', () => {
|
||||||
|
it('should return true if onlineRolesIncludePaidSubscription', () => {
|
||||||
|
featureService.onlineRolesIncludePaidSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true if hasOfflineRepo', () => {
|
||||||
|
featureService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true if hasFirstPartyOnlineSubscription', () => {
|
||||||
|
featureService.hasFirstPartyOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -148,40 +207,40 @@ describe('FeaturesService', () => {
|
|||||||
describe('updateRoles()', () => {
|
describe('updateRoles()', () => {
|
||||||
it('setRoles should notify event if roles changed', async () => {
|
it('setRoles should notify event if roles changed', async () => {
|
||||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||||
const featuresService = createService()
|
|
||||||
featuresService.initializeFromDisk()
|
|
||||||
|
|
||||||
const mock = (featuresService['notifyEvent'] = jest.fn())
|
featureService.initializeFromDisk()
|
||||||
|
|
||||||
|
const mock = (featureService['notifyEvent'] = jest.fn())
|
||||||
|
|
||||||
const newRoles = [...roles, RoleName.NAMES.PlusUser]
|
const newRoles = [...roles, RoleName.NAMES.PlusUser]
|
||||||
featuresService.setOnlineRoles(newRoles)
|
featureService.setOnlineRoles(newRoles)
|
||||||
|
|
||||||
expect(mock.mock.calls[0][0]).toEqual(FeaturesEvent.UserRolesChanged)
|
expect(mock.mock.calls[0][0]).toEqual(FeaturesEvent.UserRolesChanged)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should notify of subscription purchase', async () => {
|
it('should notify of subscription purchase', async () => {
|
||||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||||
const featuresService = createService()
|
|
||||||
featuresService.initializeFromDisk()
|
|
||||||
|
|
||||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
featureService.initializeFromDisk()
|
||||||
|
|
||||||
|
const spy = jest.spyOn(featureService, 'notifyEvent' as never)
|
||||||
|
|
||||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||||
|
|
||||||
expect(spy.mock.calls[1][0]).toEqual(FeaturesEvent.DidPurchaseSubscription)
|
expect(spy.mock.calls[1][0]).toEqual(FeaturesEvent.DidPurchaseSubscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not notify of subscription purchase on initial roles load after sign in', async () => {
|
it('should not notify of subscription purchase on initial roles load after sign in', async () => {
|
||||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||||
const featuresService = createService()
|
|
||||||
featuresService.initializeFromDisk()
|
|
||||||
featuresService['onlineRoles'] = []
|
|
||||||
|
|
||||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
featureService.initializeFromDisk()
|
||||||
|
featureService['onlineRoles'] = []
|
||||||
|
|
||||||
|
const spy = jest.spyOn(featureService, 'notifyEvent' as never)
|
||||||
|
|
||||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||||
|
|
||||||
const triggeredEvents = spy.mock.calls.map((call) => call[0])
|
const triggeredEvents = spy.mock.calls.map((call) => call[0])
|
||||||
expect(triggeredEvents).not.toContain(FeaturesEvent.DidPurchaseSubscription)
|
expect(triggeredEvents).not.toContain(FeaturesEvent.DidPurchaseSubscription)
|
||||||
@@ -189,11 +248,11 @@ describe('FeaturesService', () => {
|
|||||||
|
|
||||||
it('saves new roles to storage if a role has been added', async () => {
|
it('saves new roles to storage if a role has been added', async () => {
|
||||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||||
const featuresService = createService()
|
|
||||||
featuresService.initializeFromDisk()
|
featureService.initializeFromDisk()
|
||||||
|
|
||||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -201,134 +260,162 @@ describe('FeaturesService', () => {
|
|||||||
const newRoles = [RoleName.NAMES.CoreUser]
|
const newRoles = [RoleName.NAMES.CoreUser]
|
||||||
|
|
||||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||||
const featuresService = createService()
|
|
||||||
featuresService.initializeFromDisk()
|
featureService.initializeFromDisk()
|
||||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||||
|
|
||||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('role-based feature status', async () => {
|
it('role-based feature status', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
expect(
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.Entitled)
|
||||||
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.Entitled)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('feature status with no paid role', async () => {
|
it('feature status with no paid role', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
expect(
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.PlusEditor)).toBe(FeatureStatus.NoUserSubscription)
|
featureService.getFeatureStatus(
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NoUserSubscription)
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.PlusEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SheetsEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('role-based features while not signed into first party server', async () => {
|
it('role-based features while not signed into first party server', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser])
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.NoUserSubscription)
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('third party feature status', async () => {
|
it('third party feature status', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
itemManager.getDisplayableComponents = jest
|
itemManager.getDisplayableComponents = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue([{ identifier: 'third-party-theme' }, { identifier: 'third-party-editor', isExpired: true }])
|
.mockReturnValue([
|
||||||
|
{ uuid: '00000000-0000-0000-0000-000000000001' },
|
||||||
|
{ uuid: '00000000-0000-0000-0000-000000000002', isExpired: true },
|
||||||
|
])
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus('third-party-theme' as FeatureIdentifier)).toBe(FeatureStatus.Entitled)
|
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000001').getValue())).toBe(
|
||||||
expect(featuresService.getFeatureStatus('third-party-editor' as FeatureIdentifier)).toBe(
|
FeatureStatus.Entitled,
|
||||||
|
)
|
||||||
|
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000002').getValue())).toBe(
|
||||||
FeatureStatus.InCurrentPlanButExpired,
|
FeatureStatus.InCurrentPlanButExpired,
|
||||||
)
|
)
|
||||||
expect(featuresService.getFeatureStatus('missing-feature-identifier' as FeatureIdentifier)).toBe(
|
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000003').getValue())).toBe(
|
||||||
FeatureStatus.NoUserSubscription,
|
FeatureStatus.NoUserSubscription,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('feature status should be not entitled if no account or offline repo', async () => {
|
it('feature status should be not entitled if no account or offline repo', async () => {
|
||||||
const featuresService = createService()
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
expect(
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(
|
featureService.getFeatureStatus(
|
||||||
FeatureStatus.NoUserSubscription,
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||||
)
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('feature status for offline subscription', async () => {
|
it('feature status for offline subscription', async () => {
|
||||||
const featuresService = createService()
|
featureService.hasFirstPartyOfflineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
featureService.setOfflineRoles([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||||
|
|
||||||
featuresService.hasFirstPartyOfflineSubscription = jest.fn().mockReturnValue(true)
|
expect(
|
||||||
featuresService.setOfflineRoles([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
),
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.Entitled)
|
).toBe(FeatureStatus.Entitled)
|
||||||
|
expect(
|
||||||
|
featureService.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.Entitled)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('feature status for deprecated feature and no subscription', async () => {
|
it('feature status for deprecated feature and no subscription', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.DeprecatedFileSafe as FeatureIdentifier)).toBe(
|
expect(
|
||||||
FeatureStatus.NoUserSubscription,
|
featureService.getFeatureStatus(
|
||||||
)
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.NoUserSubscription)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('feature status for deprecated feature with subscription', async () => {
|
it('feature status for deprecated feature with subscription', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||||
|
|
||||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.DeprecatedFileSafe as FeatureIdentifier)).toBe(
|
expect(
|
||||||
FeatureStatus.Entitled,
|
featureService.getFeatureStatus(
|
||||||
)
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe).getValue(),
|
||||||
|
),
|
||||||
|
).toBe(FeatureStatus.Entitled)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has paid subscription', async () => {
|
it('has paid subscription', async () => {
|
||||||
const featuresService = createService()
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toBeFalsy
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toBeFalsy
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||||
|
|
||||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has paid subscription should be true if offline repo and signed into third party server', async () => {
|
it('has paid subscription should be true if offline repo and signed into third party server', async () => {
|
||||||
const featuresService = createService()
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
featureService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
featuresService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -343,8 +430,7 @@ describe('FeaturesService', () => {
|
|||||||
},
|
},
|
||||||
} as never)
|
} as never)
|
||||||
|
|
||||||
const featuresService = createService()
|
await featureService.migrateFeatureRepoToUserSetting([extensionRepoItem])
|
||||||
await featuresService.migrateFeatureRepoToUserSetting([extensionRepoItem])
|
|
||||||
expect(settingsService.updateSetting).toHaveBeenCalledWith(
|
expect(settingsService.updateSetting).toHaveBeenCalledWith(
|
||||||
SettingName.create(SettingName.NAMES.ExtensionKey).getValue(),
|
SettingName.create(SettingName.NAMES.ExtensionKey).getValue(),
|
||||||
extensionKey,
|
extensionKey,
|
||||||
@@ -369,8 +455,7 @@ describe('FeaturesService', () => {
|
|||||||
const installUrl = 'http://example.com'
|
const installUrl = 'http://example.com'
|
||||||
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
||||||
|
|
||||||
const featuresService = createService()
|
const result = await featureService.downloadRemoteThirdPartyFeature(installUrl)
|
||||||
const result = await featuresService.downloadRemoteThirdPartyFeature(installUrl)
|
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -389,17 +474,14 @@ describe('FeaturesService', () => {
|
|||||||
const installUrl = 'http://example.com'
|
const installUrl = 'http://example.com'
|
||||||
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
||||||
|
|
||||||
const featuresService = createService()
|
const result = await featureService.downloadRemoteThirdPartyFeature(installUrl)
|
||||||
const result = await featuresService.downloadRemoteThirdPartyFeature(installUrl)
|
|
||||||
expect(result).toBeUndefined()
|
expect(result).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sortRolesByHierarchy', () => {
|
describe('sortRolesByHierarchy', () => {
|
||||||
it('should sort given roles according to role hierarchy', () => {
|
it('should sort given roles according to role hierarchy', () => {
|
||||||
const featuresService = createService()
|
const sortedRoles = featureService.rolesBySorting([
|
||||||
|
|
||||||
const sortedRoles = featuresService.rolesBySorting([
|
|
||||||
RoleName.NAMES.ProUser,
|
RoleName.NAMES.ProUser,
|
||||||
RoleName.NAMES.CoreUser,
|
RoleName.NAMES.CoreUser,
|
||||||
RoleName.NAMES.PlusUser,
|
RoleName.NAMES.PlusUser,
|
||||||
@@ -411,50 +493,42 @@ describe('FeaturesService', () => {
|
|||||||
|
|
||||||
describe('hasMinimumRole', () => {
|
describe('hasMinimumRole', () => {
|
||||||
it('should be false if core user checks for plus role', async () => {
|
it('should be false if core user checks for plus role', async () => {
|
||||||
const featuresService = createService()
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
const hasPlusUserRole = featureService.hasMinimumRole(RoleName.NAMES.PlusUser)
|
||||||
|
|
||||||
const hasPlusUserRole = featuresService.hasMinimumRole(RoleName.NAMES.PlusUser)
|
|
||||||
|
|
||||||
expect(hasPlusUserRole).toBe(false)
|
expect(hasPlusUserRole).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be false if plus user checks for pro role', async () => {
|
it('should be false if plus user checks for pro role', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
||||||
|
|
||||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
const hasProUserRole = featureService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||||
|
|
||||||
expect(hasProUserRole).toBe(false)
|
expect(hasProUserRole).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be true if pro user checks for core user', async () => {
|
it('should be true if pro user checks for core user', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||||
|
|
||||||
const hasCoreUserRole = featuresService.hasMinimumRole(RoleName.NAMES.CoreUser)
|
const hasCoreUserRole = featureService.hasMinimumRole(RoleName.NAMES.CoreUser)
|
||||||
|
|
||||||
expect(hasCoreUserRole).toBe(true)
|
expect(hasCoreUserRole).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be true if pro user checks for pro user', async () => {
|
it('should be true if pro user checks for pro user', async () => {
|
||||||
const featuresService = createService()
|
|
||||||
|
|
||||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||||
|
|
||||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
const hasProUserRole = featureService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||||
|
|
||||||
expect(hasProUserRole).toBe(true)
|
expect(hasProUserRole).toBe(true)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { MigrateFeatureRepoToUserSettingUseCase } from './UseCase/MigrateFeatureRepoToUserSetting'
|
import { MigrateFeatureRepoToUserSettingUseCase } from './UseCase/MigrateFeatureRepoToUserSetting'
|
||||||
import { arraysEqual, removeFromArray, lastElement } from '@standardnotes/utils'
|
import { arraysEqual, removeFromArray, lastElement } from '@standardnotes/utils'
|
||||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||||
import { RoleName, ContentType } from '@standardnotes/domain-core'
|
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||||
import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
|
import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
|
||||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||||
import { WebSocketsService } from '../Api/WebsocketsService'
|
import { WebSocketsService } from '../Api/WebsocketsService'
|
||||||
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
|
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
|
||||||
import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts'
|
import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts'
|
||||||
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
|
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
|
||||||
import { ExperimentalFeatures, FindNativeFeature, FeatureIdentifier } from '@standardnotes/features'
|
import { ExperimentalFeatures, FindNativeFeature, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import {
|
import {
|
||||||
SNFeatureRepo,
|
SNFeatureRepo,
|
||||||
FeatureRepoContent,
|
FeatureRepoContent,
|
||||||
@@ -64,7 +64,7 @@ export class FeaturesService
|
|||||||
{
|
{
|
||||||
private onlineRoles: string[] = []
|
private onlineRoles: string[] = []
|
||||||
private offlineRoles: string[] = []
|
private offlineRoles: string[] = []
|
||||||
private enabledExperimentalFeatures: FeatureIdentifier[] = []
|
private enabledExperimentalFeatures: string[] = []
|
||||||
|
|
||||||
private getFeatureStatusUseCase = new GetFeatureStatusUseCase(this.items)
|
private getFeatureStatusUseCase = new GetFeatureStatusUseCase(this.items)
|
||||||
|
|
||||||
@@ -136,40 +136,47 @@ export class FeaturesService
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeFromDisk(): void {
|
initializeFromDisk(): void {
|
||||||
this.onlineRoles = this.storage.getValue<string[]>(StorageKey.UserRoles, undefined, [])
|
this.onlineRoles = this.storage.getValue<string[]>(StorageKey.UserRoles, undefined, [])
|
||||||
|
|
||||||
this.offlineRoles = this.storage.getValue<string[]>(StorageKey.OfflineUserRoles, undefined, [])
|
this.offlineRoles = this.storage.getValue<string[]>(StorageKey.OfflineUserRoles, undefined, [])
|
||||||
|
|
||||||
this.enabledExperimentalFeatures = this.storage.getValue(StorageKey.ExperimentalFeatures, undefined, [])
|
this.enabledExperimentalFeatures = this.storage.getValue(StorageKey.ExperimentalFeatures, undefined, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||||
if (event.type === ApiServiceEvent.MetaReceived) {
|
switch (event.type) {
|
||||||
if (!this.sync) {
|
case ApiServiceEvent.MetaReceived: {
|
||||||
this.log('Handling events interrupted. Sync service is not yet initialized.', event)
|
if (!this.sync) {
|
||||||
return
|
this.log('Handling events interrupted. Sync service is not yet initialized.', event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userRoles } = event.payload as MetaReceivedData
|
||||||
|
void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userRoles } = event.payload as MetaReceivedData
|
case ApplicationEvent.ApplicationStageChanged: {
|
||||||
void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name))
|
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||||
}
|
switch (stage) {
|
||||||
|
case ApplicationStage.StorageDecrypted_09: {
|
||||||
if (event.type === ApplicationEvent.ApplicationStageChanged) {
|
this.initializeFromDisk()
|
||||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
break
|
||||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
}
|
||||||
if (!this.hasFirstPartyOnlineSubscription()) {
|
case ApplicationStage.FullSyncCompleted_13: {
|
||||||
const offlineRepo = this.getOfflineRepo()
|
if (!this.hasFirstPartyOnlineSubscription()) {
|
||||||
|
const offlineRepo = this.getOfflineRepo()
|
||||||
if (offlineRepo) {
|
if (offlineRepo) {
|
||||||
void this.downloadOfflineRoles(offlineRepo)
|
void this.downloadOfflineRoles(offlineRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enableExperimentalFeature(identifier: FeatureIdentifier): void {
|
public enableExperimentalFeature(identifier: string): void {
|
||||||
this.enabledExperimentalFeatures.push(identifier)
|
this.enabledExperimentalFeatures.push(identifier)
|
||||||
|
|
||||||
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
||||||
@@ -177,7 +184,7 @@ export class FeaturesService
|
|||||||
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
public disableExperimentalFeature(identifier: FeatureIdentifier): void {
|
public disableExperimentalFeature(identifier: string): void {
|
||||||
removeFromArray(this.enabledExperimentalFeatures, identifier)
|
removeFromArray(this.enabledExperimentalFeatures, identifier)
|
||||||
|
|
||||||
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
||||||
@@ -195,7 +202,7 @@ export class FeaturesService
|
|||||||
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleExperimentalFeature(identifier: FeatureIdentifier): void {
|
public toggleExperimentalFeature(identifier: string): void {
|
||||||
if (this.isExperimentalFeatureEnabled(identifier)) {
|
if (this.isExperimentalFeatureEnabled(identifier)) {
|
||||||
this.disableExperimentalFeature(identifier)
|
this.disableExperimentalFeature(identifier)
|
||||||
} else {
|
} else {
|
||||||
@@ -203,19 +210,19 @@ export class FeaturesService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getExperimentalFeatures(): FeatureIdentifier[] {
|
public getExperimentalFeatures(): string[] {
|
||||||
return ExperimentalFeatures
|
return ExperimentalFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
public isExperimentalFeature(featureId: FeatureIdentifier): boolean {
|
public isExperimentalFeature(featureId: string): boolean {
|
||||||
return this.getExperimentalFeatures().includes(featureId)
|
return this.getExperimentalFeatures().includes(featureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEnabledExperimentalFeatures(): FeatureIdentifier[] {
|
public getEnabledExperimentalFeatures(): string[] {
|
||||||
return this.enabledExperimentalFeatures
|
return this.enabledExperimentalFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
public isExperimentalFeatureEnabled(featureId: FeatureIdentifier): boolean {
|
public isExperimentalFeatureEnabled(featureId: string): boolean {
|
||||||
return this.enabledExperimentalFeatures.includes(featureId)
|
return this.enabledExperimentalFeatures.includes(featureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,10 +309,10 @@ export class FeaturesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription(): boolean {
|
hasPaidAnyPartyOnlineOrOfflineSubscription(): boolean {
|
||||||
return this.onlineRolesIncludePaidSubscription() || this.hasOfflineRepo()
|
return this.onlineRolesIncludePaidSubscription() || this.hasOfflineRepo() || this.hasFirstPartyOnlineSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasFirstPartyOnlineSubscription(): boolean {
|
hasFirstPartyOnlineSubscription(): boolean {
|
||||||
return this.sessions.isSignedIntoFirstPartyServer() && this.subscriptions.hasOnlineSubscription()
|
return this.sessions.isSignedIntoFirstPartyServer() && this.subscriptions.hasOnlineSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,12 +371,13 @@ export class FeaturesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isThirdPartyFeature(identifier: string): boolean {
|
public isThirdPartyFeature(identifier: string): boolean {
|
||||||
const isNativeFeature = !!FindNativeFeature(identifier as FeatureIdentifier)
|
const isNativeFeature = !!FindNativeFeature(identifier)
|
||||||
return !isNativeFeature
|
return !isNativeFeature
|
||||||
}
|
}
|
||||||
|
|
||||||
onlineRolesIncludePaidSubscription(): boolean {
|
onlineRolesIncludePaidSubscription(): boolean {
|
||||||
const unpaidRoles = [RoleName.NAMES.CoreUser]
|
const unpaidRoles = [RoleName.NAMES.CoreUser]
|
||||||
|
|
||||||
return this.onlineRoles.some((role) => !unpaidRoles.includes(role))
|
return this.onlineRoles.some((role) => !unpaidRoles.includes(role))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +400,7 @@ export class FeaturesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getFeatureStatus(
|
public getFeatureStatus(
|
||||||
featureId: FeatureIdentifier,
|
featureId: NativeFeatureIdentifier | Uuid,
|
||||||
options: { inContextOfItem?: DecryptedItemInterface } = {},
|
options: { inContextOfItem?: DecryptedItemInterface } = {},
|
||||||
): FeatureStatus {
|
): FeatureStatus {
|
||||||
return this.getFeatureStatusUseCase.execute({
|
return this.getFeatureStatusUseCase.execute({
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
||||||
import { GetFeatureStatusUseCase } from './GetFeatureStatus'
|
import { GetFeatureStatusUseCase } from './GetFeatureStatus'
|
||||||
import { ComponentInterface, DecryptedItemInterface } from '@standardnotes/models'
|
import { ComponentInterface, DecryptedItemInterface } from '@standardnotes/models'
|
||||||
|
|
||||||
jest.mock('@standardnotes/features', () => ({
|
|
||||||
FeatureIdentifier: {
|
|
||||||
DarkTheme: 'darkTheme',
|
|
||||||
},
|
|
||||||
FindNativeFeature: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
import { FindNativeFeature } from '@standardnotes/features'
|
|
||||||
import { Subscription } from '@standardnotes/responses'
|
import { Subscription } from '@standardnotes/responses'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
describe('GetFeatureStatusUseCase', () => {
|
describe('GetFeatureStatusUseCase', () => {
|
||||||
let items: jest.Mocked<ItemManagerInterface>
|
let items: jest.Mocked<ItemManagerInterface>
|
||||||
let usecase: GetFeatureStatusUseCase
|
let usecase: GetFeatureStatusUseCase
|
||||||
|
let findNativeFeature: jest.Mock<any, any>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
items = {
|
items = {
|
||||||
getDisplayableComponents: jest.fn(),
|
getDisplayableComponents: jest.fn(),
|
||||||
} as unknown as jest.Mocked<ItemManagerInterface>
|
} as unknown as jest.Mocked<ItemManagerInterface>
|
||||||
usecase = new GetFeatureStatusUseCase(items)
|
usecase = new GetFeatureStatusUseCase(items)
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue(undefined)
|
findNativeFeature = jest.fn()
|
||||||
|
usecase.findNativeFeature = findNativeFeature
|
||||||
|
findNativeFeature.mockReturnValue(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -33,7 +28,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
it('should return entitled for free features', () => {
|
it('should return entitled for free features', () => {
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: FeatureIdentifier.DarkTheme,
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DarkTheme).getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
@@ -44,11 +39,11 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
describe('deprecated features', () => {
|
describe('deprecated features', () => {
|
||||||
it('should return entitled for deprecated paid features if any subscription is active', () => {
|
it('should return entitled for deprecated paid features if any subscription is active', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: true })
|
findNativeFeature.mockReturnValue({ deprecated: true })
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'deprecatedFeature',
|
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: true,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: true,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
@@ -57,11 +52,11 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return NoUserSubscription for deprecated paid features if no subscription is active', () => {
|
it('should return NoUserSubscription for deprecated paid features if no subscription is active', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: true })
|
findNativeFeature.mockReturnValue({ deprecated: true })
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'deprecatedFeature',
|
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
@@ -72,11 +67,11 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
describe('native features', () => {
|
describe('native features', () => {
|
||||||
it('should return Entitled if the context item belongs to a shared vault and user does not have subscription', () => {
|
it('should return Entitled if the context item belongs to a shared vault and user does not have subscription', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
@@ -86,11 +81,11 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return NoUserSubscription if the context item does not belong to a shared vault and user does not have subscription', () => {
|
it('should return NoUserSubscription if the context item does not belong to a shared vault and user does not have subscription', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
@@ -100,11 +95,11 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return NoUserSubscription for native features without subscription and roles', () => {
|
it('should return NoUserSubscription for native features without subscription and roles', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
@@ -113,14 +108,14 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return NotInCurrentPlan for native features with roles not in available roles', () => {
|
it('should return NotInCurrentPlan for native features with roles not in available roles', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
findNativeFeature.mockReturnValue({
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
availableInRoles: ['notInRole'],
|
availableInRoles: ['notInRole'],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: { online: ['inRole'] },
|
firstPartyRoles: { online: ['inRole'] },
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
@@ -129,14 +124,14 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return Entitled for native features with roles in available roles and active subscription', () => {
|
it('should return Entitled for native features with roles in available roles and active subscription', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
findNativeFeature.mockReturnValue({
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
availableInRoles: ['inRole'],
|
availableInRoles: ['inRole'],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: {
|
firstPartyOnlineSubscription: {
|
||||||
endsAt: new Date(Date.now() + 10000).getTime(),
|
endsAt: new Date(Date.now() + 10000).getTime(),
|
||||||
} as jest.Mocked<Subscription>,
|
} as jest.Mocked<Subscription>,
|
||||||
@@ -147,14 +142,14 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return InCurrentPlanButExpired for native features with roles in available roles and expired subscription', () => {
|
it('should return InCurrentPlanButExpired for native features with roles in available roles and expired subscription', () => {
|
||||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
findNativeFeature.mockReturnValue({
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
availableInRoles: ['inRole'],
|
availableInRoles: ['inRole'],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nativeFeature',
|
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||||
firstPartyOnlineSubscription: {
|
firstPartyOnlineSubscription: {
|
||||||
endsAt: new Date(Date.now() - 10000).getTime(),
|
endsAt: new Date(Date.now() - 10000).getTime(),
|
||||||
} as jest.Mocked<Subscription>,
|
} as jest.Mocked<Subscription>,
|
||||||
@@ -168,7 +163,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
describe('third party features', () => {
|
describe('third party features', () => {
|
||||||
it('should return Entitled for third-party features', () => {
|
it('should return Entitled for third-party features', () => {
|
||||||
const mockComponent = {
|
const mockComponent = {
|
||||||
identifier: 'thirdPartyFeature',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
} as unknown as jest.Mocked<ComponentInterface>
|
} as unknown as jest.Mocked<ComponentInterface>
|
||||||
|
|
||||||
@@ -176,7 +171,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'thirdPartyFeature',
|
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
@@ -189,7 +184,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'nonExistingThirdPartyFeature',
|
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
@@ -199,7 +194,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
it('should return InCurrentPlanButExpired for expired third-party features', () => {
|
it('should return InCurrentPlanButExpired for expired third-party features', () => {
|
||||||
const mockComponent = {
|
const mockComponent = {
|
||||||
identifier: 'thirdPartyFeature',
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
isExpired: true,
|
isExpired: true,
|
||||||
} as unknown as jest.Mocked<ComponentInterface>
|
} as unknown as jest.Mocked<ComponentInterface>
|
||||||
|
|
||||||
@@ -207,7 +202,7 @@ describe('GetFeatureStatusUseCase', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
usecase.execute({
|
usecase.execute({
|
||||||
featureId: 'thirdPartyFeature',
|
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||||
firstPartyOnlineSubscription: undefined,
|
firstPartyOnlineSubscription: undefined,
|
||||||
firstPartyRoles: undefined,
|
firstPartyRoles: undefined,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AnyFeatureDescription, FeatureIdentifier, FindNativeFeature } from '@standardnotes/features'
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { AnyFeatureDescription, NativeFeatureIdentifier, FindNativeFeature } from '@standardnotes/features'
|
||||||
import { DecryptedItemInterface } from '@standardnotes/models'
|
import { DecryptedItemInterface } from '@standardnotes/models'
|
||||||
import { Subscription } from '@standardnotes/responses'
|
import { Subscription } from '@standardnotes/responses'
|
||||||
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
||||||
@@ -8,20 +9,19 @@ export class GetFeatureStatusUseCase {
|
|||||||
constructor(private items: ItemManagerInterface) {}
|
constructor(private items: ItemManagerInterface) {}
|
||||||
|
|
||||||
execute(dto: {
|
execute(dto: {
|
||||||
featureId: FeatureIdentifier | string
|
featureId: NativeFeatureIdentifier | Uuid
|
||||||
firstPartyOnlineSubscription: Subscription | undefined
|
firstPartyOnlineSubscription: Subscription | undefined
|
||||||
firstPartyRoles: { online: string[] } | { offline: string[] } | undefined
|
firstPartyRoles: { online: string[] } | { offline: string[] } | undefined
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
||||||
inContextOfItem?: DecryptedItemInterface
|
inContextOfItem?: DecryptedItemInterface
|
||||||
}): FeatureStatus {
|
}): FeatureStatus {
|
||||||
if (this.isFreeFeature(dto.featureId as FeatureIdentifier)) {
|
if (this.isFreeFeature(dto.featureId)) {
|
||||||
return FeatureStatus.Entitled
|
return FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|
||||||
const nativeFeature = FindNativeFeature(dto.featureId as FeatureIdentifier)
|
const nativeFeature = this.findNativeFeature(dto.featureId)
|
||||||
|
|
||||||
if (!nativeFeature) {
|
if (!nativeFeature) {
|
||||||
return this.getThirdPartyFeatureStatus(dto.featureId as string)
|
return this.getThirdPartyFeatureStatus(dto.featureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nativeFeature.deprecated) {
|
if (nativeFeature.deprecated) {
|
||||||
@@ -39,6 +39,10 @@ export class GetFeatureStatusUseCase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findNativeFeature(featureId: NativeFeatureIdentifier | Uuid): AnyFeatureDescription | undefined {
|
||||||
|
return FindNativeFeature(featureId.value)
|
||||||
|
}
|
||||||
|
|
||||||
private getDeprecatedNativeFeatureStatus(dto: {
|
private getDeprecatedNativeFeatureStatus(dto: {
|
||||||
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
||||||
nativeFeature: AnyFeatureDescription
|
nativeFeature: AnyFeatureDescription
|
||||||
@@ -95,8 +99,8 @@ export class GetFeatureStatusUseCase {
|
|||||||
return FeatureStatus.Entitled
|
return FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|
||||||
private getThirdPartyFeatureStatus(featureId: string): FeatureStatus {
|
private getThirdPartyFeatureStatus(uuid: Uuid): FeatureStatus {
|
||||||
const component = this.items.getDisplayableComponents().find((candidate) => candidate.identifier === featureId)
|
const component = this.items.getDisplayableComponents().find((candidate) => candidate.uuid === uuid.value)
|
||||||
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return FeatureStatus.NoUserSubscription
|
return FeatureStatus.NoUserSubscription
|
||||||
@@ -109,7 +113,9 @@ export class GetFeatureStatusUseCase {
|
|||||||
return FeatureStatus.Entitled
|
return FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|
||||||
private isFreeFeature(featureId: FeatureIdentifier) {
|
private isFreeFeature(featureId: NativeFeatureIdentifier) {
|
||||||
return [FeatureIdentifier.DarkTheme, FeatureIdentifier.PlainEditor].includes(featureId)
|
return [NativeFeatureIdentifier.TYPES.DarkTheme, NativeFeatureIdentifier.TYPES.PlainEditor].includes(
|
||||||
|
featureId.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ import {
|
|||||||
UserKeyPairChangedEventData,
|
UserKeyPairChangedEventData,
|
||||||
InternalFeatureService,
|
InternalFeatureService,
|
||||||
InternalFeature,
|
InternalFeature,
|
||||||
|
ApplicationEvent,
|
||||||
|
ApplicationStageChangedEventPayload,
|
||||||
|
ApplicationStage,
|
||||||
} from '@standardnotes/services'
|
} from '@standardnotes/services'
|
||||||
import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||||
import {
|
import {
|
||||||
@@ -112,8 +115,17 @@ export class SessionManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||||
if (event.type === ApiServiceEvent.SessionRefreshed) {
|
switch (event.type) {
|
||||||
this.httpService.setSession((event.payload as SessionRefreshedData).session)
|
case ApiServiceEvent.SessionRefreshed:
|
||||||
|
this.httpService.setSession((event.payload as SessionRefreshedData).session)
|
||||||
|
break
|
||||||
|
|
||||||
|
case ApplicationEvent.ApplicationStageChanged: {
|
||||||
|
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||||
|
if (stage === ApplicationStage.StorageDecrypted_09) {
|
||||||
|
await this.initializeFromDisk()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +154,7 @@ export class SessionManager
|
|||||||
this.apiService.setUser(user)
|
this.apiService.setUser(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeFromDisk(): Promise<void> {
|
private async initializeFromDisk(): Promise<void> {
|
||||||
this.memoizeUser(this.storage.getValue(StorageKey.User))
|
this.memoizeUser(this.storage.getValue(StorageKey.User))
|
||||||
|
|
||||||
if (!this.user) {
|
if (!this.user) {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ describe('migrations', () => {
|
|||||||
content_type: ContentType.TYPES.Component,
|
content_type: ContentType.TYPES.Component,
|
||||||
content: FillItemContent({
|
content: FillItemContent({
|
||||||
package_info: {
|
package_info: {
|
||||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -170,7 +170,7 @@ describe('migrations', () => {
|
|||||||
content_type: ContentType.TYPES.Component,
|
content_type: ContentType.TYPES.Component,
|
||||||
content: FillItemContent({
|
content: FillItemContent({
|
||||||
package_info: {
|
package_info: {
|
||||||
identifier: FeatureIdentifier.DeprecatedBoldEditor,
|
identifier: NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -369,9 +369,9 @@ describe('app models', () => {
|
|||||||
editorIdentifier: 'foo-editor',
|
editorIdentifier: 'foo-editor',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(this.application.componentManager.editorForNote(note).uniqueIdentifier).to.equal(component.uuid)
|
expect(this.application.componentManager.editorForNote(note).uniqueIdentifier.value).to.equal(component.uuid)
|
||||||
|
|
||||||
const duplicate = await this.application.mutator.duplicateItem(note, true)
|
const duplicate = await this.application.mutator.duplicateItem(note, true)
|
||||||
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier).to.equal(component.uuid)
|
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"tsc": "tsc --project tsconfig.json",
|
"tsc": "tsc --project tsconfig.json",
|
||||||
"lint": "eslint src --ext .ts",
|
"lint": "eslint src --ext .ts",
|
||||||
"test": "jest spec"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standardnotes/common": "^1.50.0",
|
"@standardnotes/common": "^1.50.0",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { WebApplicationInterface } from './../../WebApplication/WebApplicationInterface'
|
import { WebApplicationInterface } from './../../WebApplication/WebApplicationInterface'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { AegisToAuthenticatorConverter } from './AegisToAuthenticatorConverter'
|
import { AegisToAuthenticatorConverter } from './AegisToAuthenticatorConverter'
|
||||||
import data from './testData'
|
import data from './testData'
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ describe('AegisConverter', () => {
|
|||||||
'[{"service":"TestMail","account":"test@test.com","secret":"TESTMAILTESTMAILTESTMAILTESTMAIL","notes":"Some note"},{"service":"Some Service","account":"test@test.com","secret":"SOMESERVICESOMESERVICESOMESERVIC","notes":"Some other service"}]',
|
'[{"service":"TestMail","account":"test@test.com","secret":"TESTMAILTESTMAILTESTMAILTESTMAIL","notes":"Some note"},{"service":"Some Service","account":"test@test.com","secret":"SOMESERVICESOMESERVICESOMESERVIC","notes":"Some other service"}]',
|
||||||
)
|
)
|
||||||
expect(result.content.noteType).toBe(NoteType.Authentication)
|
expect(result.content.noteType).toBe(NoteType.Authentication)
|
||||||
expect(result.content.editorIdentifier).toBe(FeatureIdentifier.TokenVaultEditor)
|
expect(result.content.editorIdentifier).toBe(NativeFeatureIdentifier.TYPES.TokenVaultEditor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create note from entries without editor info', () => {
|
it('should create note from entries without editor info', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
|
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
|
||||||
import { readFileAsText } from '../Utils'
|
import { readFileAsText } from '../Utils'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { WebApplicationInterface } from '../../WebApplication/WebApplicationInterface'
|
import { WebApplicationInterface } from '../../WebApplication/WebApplicationInterface'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export class AegisToAuthenticatorConverter {
|
|||||||
references: [],
|
references: [],
|
||||||
...(addEditorInfo && {
|
...(addEditorInfo && {
|
||||||
noteType: NoteType.Authentication,
|
noteType: NoteType.Authentication,
|
||||||
editorIdentifier: FeatureIdentifier.TokenVaultEditor,
|
editorIdentifier: NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { parseFileName } from '@standardnotes/filepicker'
|
import { parseFileName } from '@standardnotes/filepicker'
|
||||||
import { FeatureStatus } from '@standardnotes/services'
|
import { FeatureStatus } from '@standardnotes/services'
|
||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { AegisToAuthenticatorConverter } from './AegisConverter/AegisToAuthenticatorConverter'
|
import { AegisToAuthenticatorConverter } from './AegisConverter/AegisToAuthenticatorConverter'
|
||||||
import { EvernoteConverter } from './EvernoteConverter/EvernoteConverter'
|
import { EvernoteConverter } from './EvernoteConverter/EvernoteConverter'
|
||||||
import { GoogleKeepConverter } from './GoogleKeepConverter/GoogleKeepConverter'
|
import { GoogleKeepConverter } from './GoogleKeepConverter/GoogleKeepConverter'
|
||||||
@@ -64,7 +64,9 @@ export class Importer {
|
|||||||
async getPayloadsFromFile(file: File, type: NoteImportType): Promise<DecryptedTransferPayload[]> {
|
async getPayloadsFromFile(file: File, type: NoteImportType): Promise<DecryptedTransferPayload[]> {
|
||||||
if (type === 'aegis') {
|
if (type === 'aegis') {
|
||||||
const isEntitledToAuthenticator =
|
const isEntitledToAuthenticator =
|
||||||
this.application.features.getFeatureStatus(FeatureIdentifier.TokenVaultEditor) === FeatureStatus.Entitled
|
this.application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
|
||||||
|
) === FeatureStatus.Entitled
|
||||||
return [await this.aegisConverter.convertAegisBackupFileToNote(file, isEntitledToAuthenticator)]
|
return [await this.aegisConverter.convertAegisBackupFileToNote(file, isEntitledToAuthenticator)]
|
||||||
} else if (type === 'google-keep') {
|
} else if (type === 'google-keep') {
|
||||||
return [await this.googleKeepConverter.convertGoogleKeepBackupFileToNote(file, true)]
|
return [await this.googleKeepConverter.convertGoogleKeepBackupFileToNote(file, true)]
|
||||||
|
|||||||
58
packages/ui-services/src/Theme/ActiveThemeList.spec.ts
Normal file
58
packages/ui-services/src/Theme/ActiveThemeList.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { ActiveThemeList } from './ActiveThemeList'
|
||||||
|
import { ItemManagerInterface } from '@standardnotes/services'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
describe('ActiveThemeList', () => {
|
||||||
|
let itemManager: ItemManagerInterface
|
||||||
|
let list: ActiveThemeList
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
itemManager = {} as ItemManagerInterface
|
||||||
|
itemManager.findItem = jest.fn()
|
||||||
|
|
||||||
|
list = new ActiveThemeList(itemManager)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should initialize with an empty list', () => {
|
||||||
|
expect(list.getList()).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be empty initially', () => {
|
||||||
|
expect(list.isEmpty()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not have items that have not been added', () => {
|
||||||
|
const uuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||||
|
expect(list.has(uuid)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add an item to the list', () => {
|
||||||
|
const uuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||||
|
list.add(uuid)
|
||||||
|
expect(list.getList()).toContain(uuid)
|
||||||
|
expect(list.has(uuid)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not add a duplicate item to the list', () => {
|
||||||
|
const uuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||||
|
list.add(uuid)
|
||||||
|
list.add(uuid)
|
||||||
|
expect(list.getList()).toEqual([uuid])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove an item from the list', () => {
|
||||||
|
const uuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||||
|
list.add(uuid)
|
||||||
|
list.remove(uuid)
|
||||||
|
expect(list.getList()).not.toContain(uuid)
|
||||||
|
expect(list.has(uuid)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear the list', () => {
|
||||||
|
const uuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||||
|
list.add(uuid)
|
||||||
|
list.clear()
|
||||||
|
expect(list.getList()).toEqual([])
|
||||||
|
expect(list.has(uuid)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
70
packages/ui-services/src/Theme/ActiveThemeList.ts
Normal file
70
packages/ui-services/src/Theme/ActiveThemeList.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { UIFeature, ThemeInterface } from '@standardnotes/models'
|
||||||
|
import { ItemManagerInterface } from '@standardnotes/services'
|
||||||
|
import { NativeFeatureIdentifier, FindNativeTheme, ThemeFeatureDescription } from '@standardnotes/features'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
export class ActiveThemeList {
|
||||||
|
private list: (NativeFeatureIdentifier | Uuid)[] = []
|
||||||
|
|
||||||
|
constructor(private items: ItemManagerInterface, initialList?: (NativeFeatureIdentifier | Uuid)[]) {
|
||||||
|
if (initialList) {
|
||||||
|
this.list = initialList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getList(): (NativeFeatureIdentifier | Uuid)[] {
|
||||||
|
return this.list.slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
public isEmpty(): boolean {
|
||||||
|
return this.list.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.list = []
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(candidate: NativeFeatureIdentifier | Uuid): boolean {
|
||||||
|
for (const entry of this.list) {
|
||||||
|
if (entry.equals(candidate)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(candidate: NativeFeatureIdentifier | Uuid): void {
|
||||||
|
if (!this.has(candidate)) {
|
||||||
|
this.list.push(candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(candidate: NativeFeatureIdentifier | Uuid): void {
|
||||||
|
this.list = this.list.filter((entry) => {
|
||||||
|
return !entry.equals(candidate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public asThemes(): UIFeature<ThemeFeatureDescription>[] {
|
||||||
|
const results: UIFeature<ThemeFeatureDescription>[] = []
|
||||||
|
|
||||||
|
for (const entry of this.list) {
|
||||||
|
if (entry instanceof Uuid) {
|
||||||
|
const theme = this.items.findItem<ThemeInterface>(entry.value)
|
||||||
|
if (theme) {
|
||||||
|
const uiFeature = new UIFeature<ThemeFeatureDescription>(theme)
|
||||||
|
results.push(uiFeature)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const theme = FindNativeTheme(entry.value)
|
||||||
|
if (theme) {
|
||||||
|
const uiFeature = new UIFeature<ThemeFeatureDescription>(theme)
|
||||||
|
results.push(uiFeature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
PrefKey,
|
PrefKey,
|
||||||
ThemeInterface,
|
ThemeInterface,
|
||||||
} from '@standardnotes/models'
|
} from '@standardnotes/models'
|
||||||
import { removeFromArray } from '@standardnotes/utils'
|
|
||||||
import {
|
import {
|
||||||
InternalEventBusInterface,
|
InternalEventBusInterface,
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
@@ -16,17 +15,19 @@ import {
|
|||||||
PreferencesServiceEvent,
|
PreferencesServiceEvent,
|
||||||
ComponentManagerInterface,
|
ComponentManagerInterface,
|
||||||
} from '@standardnotes/services'
|
} from '@standardnotes/services'
|
||||||
import { FeatureIdentifier, FindNativeTheme, ThemeFeatureDescription } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, FindNativeTheme, ThemeFeatureDescription } from '@standardnotes/features'
|
||||||
import { WebApplicationInterface } from '../WebApplication/WebApplicationInterface'
|
import { WebApplicationInterface } from '../WebApplication/WebApplicationInterface'
|
||||||
import { AbstractUIServicee } from '../Abstract/AbstractUIService'
|
import { AbstractUIServicee } from '../Abstract/AbstractUIService'
|
||||||
import { GetAllThemesUseCase } from './GetAllThemesUseCase'
|
import { GetAllThemesUseCase } from './GetAllThemesUseCase'
|
||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { ActiveThemeList } from './ActiveThemeList'
|
||||||
|
|
||||||
const CachedThemesKey = 'cachedThemes'
|
const CachedThemesKey = 'cachedThemes'
|
||||||
const TimeBeforeApplyingColorScheme = 5
|
const TimeBeforeApplyingColorScheme = 5
|
||||||
const DefaultThemeIdentifier = 'Default'
|
const DefaultThemeIdentifier = 'Default'
|
||||||
|
|
||||||
export class ThemeManager extends AbstractUIServicee {
|
export class ThemeManager extends AbstractUIServicee {
|
||||||
private themesActiveInTheUI: string[] = []
|
private themesActiveInTheUI: ActiveThemeList
|
||||||
private lastUseDeviceThemeSettings = false
|
private lastUseDeviceThemeSettings = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -37,6 +38,23 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
) {
|
) {
|
||||||
super(application, internalEventBus)
|
super(application, internalEventBus)
|
||||||
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this)
|
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this)
|
||||||
|
this.themesActiveInTheUI = new ActiveThemeList(application.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
override deinit() {
|
||||||
|
this.themesActiveInTheUI.clear()
|
||||||
|
;(this.themesActiveInTheUI as unknown) = undefined
|
||||||
|
;(this.preferences as unknown) = undefined
|
||||||
|
;(this.components as unknown) = undefined
|
||||||
|
|
||||||
|
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
if (mq.removeEventListener != undefined) {
|
||||||
|
mq.removeEventListener('change', this.colorSchemeEventHandler)
|
||||||
|
} else {
|
||||||
|
mq.removeListener(this.colorSchemeEventHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.deinit()
|
||||||
}
|
}
|
||||||
|
|
||||||
override async onAppStart() {
|
override async onAppStart() {
|
||||||
@@ -66,20 +84,32 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
|
|
||||||
let hasChange = false
|
let hasChange = false
|
||||||
|
|
||||||
const activeThemes = this.components.getActiveThemesIdentifiers()
|
const { features, uuids } = this.components.getActiveThemesIdentifiers()
|
||||||
for (const uiActiveTheme of this.themesActiveInTheUI) {
|
|
||||||
if (!activeThemes.includes(uiActiveTheme)) {
|
const featuresList = new ActiveThemeList(this.application.items, features)
|
||||||
this.deactivateThemeInTheUI(uiActiveTheme)
|
const uuidsList = new ActiveThemeList(this.application.items, uuids)
|
||||||
|
|
||||||
|
for (const active of this.themesActiveInTheUI.getList()) {
|
||||||
|
if (!featuresList.has(active) && !uuidsList.has(active)) {
|
||||||
|
this.deactivateThemeInTheUI(active)
|
||||||
hasChange = true
|
hasChange = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const activeTheme of activeThemes) {
|
for (const feature of features) {
|
||||||
if (!this.themesActiveInTheUI.includes(activeTheme)) {
|
if (!this.themesActiveInTheUI.has(feature)) {
|
||||||
const theme =
|
const theme = FindNativeTheme(feature.value)
|
||||||
FindNativeTheme(activeTheme as FeatureIdentifier) ??
|
if (theme) {
|
||||||
this.application.items.findItem<ThemeInterface>(activeTheme)
|
const uiFeature = new UIFeature<ThemeFeatureDescription>(theme)
|
||||||
|
this.activateTheme(uiFeature)
|
||||||
|
hasChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const uuid of uuids) {
|
||||||
|
if (!this.themesActiveInTheUI.has(uuid)) {
|
||||||
|
const theme = this.application.items.findItem<ThemeInterface>(uuid.value)
|
||||||
if (theme) {
|
if (theme) {
|
||||||
const uiFeature = new UIFeature<ThemeFeatureDescription>(theme)
|
const uiFeature = new UIFeature<ThemeFeatureDescription>(theme)
|
||||||
this.activateTheme(uiFeature)
|
this.activateTheme(uiFeature)
|
||||||
@@ -99,7 +129,7 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case ApplicationEvent.SignedOut: {
|
case ApplicationEvent.SignedOut: {
|
||||||
this.deactivateAllThemes()
|
this.deactivateAllThemes()
|
||||||
this.themesActiveInTheUI = []
|
this.themesActiveInTheUI.clear()
|
||||||
this.application?.removeValue(CachedThemesKey, StorageValueModes.Nonwrapped).catch(console.error)
|
this.application?.removeValue(CachedThemesKey, StorageValueModes.Nonwrapped).catch(console.error)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -158,35 +188,13 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override deinit() {
|
|
||||||
this.themesActiveInTheUI = []
|
|
||||||
|
|
||||||
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
||||||
if (mq.removeEventListener != undefined) {
|
|
||||||
mq.removeEventListener('change', this.colorSchemeEventHandler)
|
|
||||||
} else {
|
|
||||||
mq.removeListener(this.colorSchemeEventHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.deinit()
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleFeaturesAvailabilityChanged(): void {
|
private handleFeaturesAvailabilityChanged(): void {
|
||||||
let hasChange = false
|
let hasChange = false
|
||||||
|
|
||||||
for (const themeUuid of this.themesActiveInTheUI) {
|
for (const theme of this.themesActiveInTheUI.asThemes()) {
|
||||||
const theme = this.application.items.findItem<ThemeInterface>(themeUuid)
|
const status = this.application.features.getFeatureStatus(theme.uniqueIdentifier)
|
||||||
|
|
||||||
if (!theme) {
|
|
||||||
this.deactivateThemeInTheUI(themeUuid)
|
|
||||||
hasChange = true
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = this.application.features.getFeatureStatus(theme.identifier)
|
|
||||||
if (status !== FeatureStatus.Entitled) {
|
if (status !== FeatureStatus.Entitled) {
|
||||||
this.deactivateThemeInTheUI(theme.uuid)
|
this.deactivateThemeInTheUI(theme.uniqueIdentifier)
|
||||||
hasChange = true
|
hasChange = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +202,7 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
const activeThemes = this.components.getActiveThemes()
|
const activeThemes = this.components.getActiveThemes()
|
||||||
|
|
||||||
for (const theme of activeThemes) {
|
for (const theme of activeThemes) {
|
||||||
if (!this.themesActiveInTheUI.includes(theme.uniqueIdentifier)) {
|
if (!this.themesActiveInTheUI.has(theme.uniqueIdentifier)) {
|
||||||
this.activateTheme(theme)
|
this.activateTheme(theme)
|
||||||
hasChange = true
|
hasChange = true
|
||||||
}
|
}
|
||||||
@@ -245,7 +253,7 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
||||||
|
|
||||||
const preferenceDefault =
|
const preferenceDefault =
|
||||||
preference === PrefKey.AutoDarkThemeIdentifier ? FeatureIdentifier.DarkTheme : DefaultThemeIdentifier
|
preference === PrefKey.AutoDarkThemeIdentifier ? NativeFeatureIdentifier.TYPES.DarkTheme : DefaultThemeIdentifier
|
||||||
|
|
||||||
const usecase = new GetAllThemesUseCase(this.application.items)
|
const usecase = new GetAllThemesUseCase(this.application.items)
|
||||||
const { thirdParty, native } = usecase.execute({ excludeLayerable: false })
|
const { thirdParty, native } = usecase.execute({ excludeLayerable: false })
|
||||||
@@ -289,21 +297,20 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private deactivateAllThemes() {
|
private deactivateAllThemes() {
|
||||||
const activeThemes = this.themesActiveInTheUI.slice()
|
const activeThemes = this.themesActiveInTheUI.getList()
|
||||||
|
|
||||||
for (const uuid of activeThemes) {
|
for (const uuid of activeThemes) {
|
||||||
this.deactivateThemeInTheUI(uuid)
|
this.deactivateThemeInTheUI(uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private activateTheme(theme: UIFeature<ThemeFeatureDescription>, skipEntitlementCheck = false) {
|
private activateTheme(theme: UIFeature<ThemeFeatureDescription>, skipEntitlementCheck = false) {
|
||||||
if (this.themesActiveInTheUI.find((uuid) => uuid === theme.uniqueIdentifier)) {
|
if (this.themesActiveInTheUI.has(theme.uniqueIdentifier)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!skipEntitlementCheck &&
|
!skipEntitlementCheck &&
|
||||||
this.application.features.getFeatureStatus(theme.featureIdentifier) !== FeatureStatus.Entitled
|
this.application.features.getFeatureStatus(theme.uniqueIdentifier) !== FeatureStatus.Entitled
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -313,14 +320,14 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.themesActiveInTheUI.push(theme.uniqueIdentifier)
|
this.themesActiveInTheUI.add(theme.uniqueIdentifier)
|
||||||
|
|
||||||
const link = document.createElement('link')
|
const link = document.createElement('link')
|
||||||
link.href = url
|
link.href = url
|
||||||
link.type = 'text/css'
|
link.type = 'text/css'
|
||||||
link.rel = 'stylesheet'
|
link.rel = 'stylesheet'
|
||||||
link.media = 'screen,print'
|
link.media = 'screen,print'
|
||||||
link.id = theme.uniqueIdentifier
|
link.id = theme.uniqueIdentifier.value
|
||||||
link.onload = () => {
|
link.onload = () => {
|
||||||
this.syncThemeColorMetadata()
|
this.syncThemeColorMetadata()
|
||||||
|
|
||||||
@@ -336,20 +343,20 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
document.getElementsByTagName('head')[0].appendChild(link)
|
document.getElementsByTagName('head')[0].appendChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateThemeInTheUI(uuid: string) {
|
private deactivateThemeInTheUI(id: NativeFeatureIdentifier | Uuid) {
|
||||||
if (!this.themesActiveInTheUI.includes(uuid)) {
|
if (!this.themesActiveInTheUI.has(id)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = document.getElementById(uuid) as HTMLLinkElement
|
const element = document.getElementById(id.value) as HTMLLinkElement
|
||||||
if (element) {
|
if (element) {
|
||||||
element.disabled = true
|
element.disabled = true
|
||||||
element.parentNode?.removeChild(element)
|
element.parentNode?.removeChild(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromArray(this.themesActiveInTheUI, uuid)
|
this.themesActiveInTheUI.remove(id)
|
||||||
|
|
||||||
if (this.themesActiveInTheUI.length === 0 && this.application.isNativeMobileWeb()) {
|
if (this.themesActiveInTheUI.isEmpty() && this.application.isNativeMobileWeb()) {
|
||||||
this.application.mobileDevice().handleThemeSchemeChange(false, '#ffffff')
|
this.application.mobileDevice().handleThemeSchemeChange(false, '#ffffff')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,11 +380,16 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async cacheThemeState() {
|
private async cacheThemeState() {
|
||||||
const themes = this.application.items.findItems<ThemeInterface>(this.themesActiveInTheUI)
|
const themes = this.themesActiveInTheUI.asThemes()
|
||||||
|
|
||||||
const mapped = themes.map((theme) => {
|
const mapped = themes.map((theme) => {
|
||||||
const payload = theme.payloadRepresentation()
|
if (theme.isComponent) {
|
||||||
return CreateDecryptedLocalStorageContextPayload(payload)
|
const payload = theme.asComponent.payloadRepresentation()
|
||||||
|
return CreateDecryptedLocalStorageContextPayload(payload)
|
||||||
|
} else {
|
||||||
|
const payload = theme.asFeatureDescription
|
||||||
|
return payload
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.application.setValue(CachedThemesKey, mapped, StorageValueModes.Nonwrapped)
|
return this.application.setValue(CachedThemesKey, mapped, StorageValueModes.Nonwrapped)
|
||||||
@@ -389,16 +401,25 @@ export class ThemeManager extends AbstractUIServicee {
|
|||||||
StorageValueModes.Nonwrapped,
|
StorageValueModes.Nonwrapped,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (cachedThemes) {
|
if (!cachedThemes) {
|
||||||
const themes: ThemeInterface[] = []
|
|
||||||
for (const cachedTheme of cachedThemes) {
|
|
||||||
const payload = this.application.items.createPayloadFromObject(cachedTheme)
|
|
||||||
const theme = this.application.items.createItemFromPayload<ThemeInterface>(payload)
|
|
||||||
themes.push(theme)
|
|
||||||
}
|
|
||||||
return themes.map((theme) => new UIFeature<ThemeFeatureDescription>(theme))
|
|
||||||
} else {
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const features: UIFeature<ThemeFeatureDescription>[] = []
|
||||||
|
|
||||||
|
for (const cachedTheme of cachedThemes) {
|
||||||
|
if ('uuid' in cachedTheme) {
|
||||||
|
const payload = this.application.items.createPayloadFromObject(cachedTheme)
|
||||||
|
const theme = this.application.items.createItemFromPayload<ThemeInterface>(payload)
|
||||||
|
features.push(new UIFeature<ThemeFeatureDescription>(theme))
|
||||||
|
} else if ('identifier' in cachedTheme) {
|
||||||
|
const feature = FindNativeTheme((cachedTheme as ThemeFeatureDescription).identifier)
|
||||||
|
if (feature) {
|
||||||
|
features.push(new UIFeature<ThemeFeatureDescription>(feature))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return features
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"prebuild": "yarn clean",
|
"prebuild": "yarn clean",
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"lint": "eslint src --ext .ts",
|
"lint": "eslint src --ext .ts",
|
||||||
"test": "jest spec"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standardnotes/common": "^1.50.0",
|
"@standardnotes/common": "^1.50.0",
|
||||||
|
|||||||
@@ -1,11 +1,46 @@
|
|||||||
import * as DOMPurifyLib from 'dompurify'
|
import * as DOMPurifyLib from 'dompurify'
|
||||||
import { JSDOM } from 'jsdom'
|
import { JSDOM } from 'jsdom'
|
||||||
import { sortByKey, withoutLastElement } from './Utils'
|
import { sortByKey, withoutLastElement, compareArrayReferences } from './Utils'
|
||||||
|
|
||||||
const window = new JSDOM('').window
|
const window = new JSDOM('').window
|
||||||
const DOMPurify = DOMPurifyLib(window as never)
|
const DOMPurify = DOMPurifyLib(window as never)
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
|
describe('compareArrayReferences', () => {
|
||||||
|
it('should return true when both arrays are empty', () => {
|
||||||
|
expect(compareArrayReferences([], [])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true when both arrays have the same reference', () => {
|
||||||
|
const obj = {}
|
||||||
|
expect(compareArrayReferences([obj], [obj])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false when arrays have different lengths', () => {
|
||||||
|
const obj1 = {}
|
||||||
|
const obj2 = {}
|
||||||
|
expect(compareArrayReferences([obj1], [obj1, obj2])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false when arrays have the same length but different references', () => {
|
||||||
|
const obj1 = {}
|
||||||
|
const obj2 = {}
|
||||||
|
expect(compareArrayReferences([obj1], [obj2])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true when arrays have multiple identical references', () => {
|
||||||
|
const obj1 = {}
|
||||||
|
const obj2 = {}
|
||||||
|
expect(compareArrayReferences([obj1, obj2], [obj1, obj2])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false when arrays have the same references in different order', () => {
|
||||||
|
const obj1 = {}
|
||||||
|
const obj2 = {}
|
||||||
|
expect(compareArrayReferences([obj1, obj2], [obj2, obj1])).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('sanitizeHtmlString', () => {
|
it('sanitizeHtmlString', () => {
|
||||||
const dirty = '<svg><animate onbegin=alert(1) attributeName=x dur=1s>'
|
const dirty = '<svg><animate onbegin=alert(1) attributeName=x dur=1s>'
|
||||||
const cleaned = DOMPurify.sanitize(dirty)
|
const cleaned = DOMPurify.sanitize(dirty)
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ export function arrayByDifference<T>(array: T[], subtract: T[]): T[] {
|
|||||||
return array.filter((x) => !subtract.includes(x)).concat(subtract.filter((x) => !array.includes(x)))
|
return array.filter((x) => !subtract.includes(x)).concat(subtract.filter((x) => !array.includes(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compareArrayReferences<T>(arr1: T[], arr2: T[]) {
|
||||||
|
return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index])
|
||||||
|
}
|
||||||
|
|
||||||
export function compareValues<T>(left: T, right: T) {
|
export function compareValues<T>(left: T, right: T) {
|
||||||
if ((left && !right) || (!left && right)) {
|
if ((left && !right) || (!left && right)) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { WebApplication } from '@/Application/WebApplication'
|
|||||||
import {
|
import {
|
||||||
UIFeature,
|
UIFeature,
|
||||||
EditorFeatureDescription,
|
EditorFeatureDescription,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
NoteMutator,
|
NoteMutator,
|
||||||
NoteType,
|
NoteType,
|
||||||
@@ -100,7 +100,7 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
|
|
||||||
setCurrentFeature(application.componentManager.editorForNote(note))
|
setCurrentFeature(application.componentManager.editorForNote(note))
|
||||||
|
|
||||||
if (uiFeature.featureIdentifier === FeatureIdentifier.PlainEditor) {
|
if (uiFeature.featureIdentifier === NativeFeatureIdentifier.TYPES.PlainEditor) {
|
||||||
reloadFont(application.getPreference(PrefKey.EditorMonospaceEnabled))
|
reloadFont(application.getPreference(PrefKey.EditorMonospaceEnabled))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -211,7 +211,7 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuRadioButtonItem
|
<MenuRadioButtonItem
|
||||||
key={menuItem.uiFeature.uniqueIdentifier}
|
key={menuItem.uiFeature.uniqueIdentifier.value}
|
||||||
onClick={onClickEditorItem}
|
onClick={onClickEditorItem}
|
||||||
className={'flex-row-reversed py-2'}
|
className={'flex-row-reversed py-2'}
|
||||||
checked={isSelected(menuItem)}
|
checked={isSelected(menuItem)}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ const ChangeEditorMultipleMenu = ({ application, notes, setDisableClickOutside }
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={item.uiFeature.uniqueIdentifier}
|
key={item.uiFeature.uniqueIdentifier.value}
|
||||||
onClick={onClickEditorItem}
|
onClick={onClickEditorItem}
|
||||||
className={'flex-row-reversed py-2'}
|
className={'flex-row-reversed py-2'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ContentType,
|
ContentType,
|
||||||
DecryptedItem,
|
DecryptedItem,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
NoteContent,
|
NoteContent,
|
||||||
NoteType,
|
NoteType,
|
||||||
@@ -61,7 +61,10 @@ const ClipperView = ({
|
|||||||
|
|
||||||
const [user, setUser] = useState(() => application.getUser())
|
const [user, setUser] = useState(() => application.getUser())
|
||||||
const [isEntitledToExtension, setIsEntitled] = useState(
|
const [isEntitledToExtension, setIsEntitled] = useState(
|
||||||
() => application.features.getFeatureStatus(FeatureIdentifier.Extension) === FeatureStatus.Entitled,
|
() =>
|
||||||
|
application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.Clipper).getValue(),
|
||||||
|
) === FeatureStatus.Entitled,
|
||||||
)
|
)
|
||||||
const isEntitledRef = useStateRef(isEntitledToExtension)
|
const isEntitledRef = useStateRef(isEntitledToExtension)
|
||||||
const hasSubscription = application.hasValidFirstPartySubscription()
|
const hasSubscription = application.hasValidFirstPartySubscription()
|
||||||
@@ -72,10 +75,18 @@ const ClipperView = ({
|
|||||||
case ApplicationEvent.SignedOut:
|
case ApplicationEvent.SignedOut:
|
||||||
case ApplicationEvent.UserRolesChanged:
|
case ApplicationEvent.UserRolesChanged:
|
||||||
setUser(application.getUser())
|
setUser(application.getUser())
|
||||||
setIsEntitled(application.features.getFeatureStatus(FeatureIdentifier.Extension) === FeatureStatus.Entitled)
|
setIsEntitled(
|
||||||
|
application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.Clipper).getValue(),
|
||||||
|
) === FeatureStatus.Entitled,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case ApplicationEvent.FeaturesAvailabilityChanged:
|
case ApplicationEvent.FeaturesAvailabilityChanged:
|
||||||
setIsEntitled(application.features.getFeatureStatus(FeatureIdentifier.Extension) === FeatureStatus.Entitled)
|
setIsEntitled(
|
||||||
|
application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.Clipper).getValue(),
|
||||||
|
) === FeatureStatus.Entitled,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -212,7 +223,7 @@ const ClipperView = ({
|
|||||||
const note = application.items.createTemplateItem<NoteContent, SNNote>(ContentType.TYPES.Note, {
|
const note = application.items.createTemplateItem<NoteContent, SNNote>(ContentType.TYPES.Note, {
|
||||||
title: clipPayload.title,
|
title: clipPayload.title,
|
||||||
text: editorStateJSON,
|
text: editorStateJSON,
|
||||||
editorIdentifier: FeatureIdentifier.SuperEditor,
|
editorIdentifier: NativeFeatureIdentifier.TYPES.SuperEditor,
|
||||||
noteType: NoteType.Super,
|
noteType: NoteType.Super,
|
||||||
references: [],
|
references: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ const IframeFeatureView: FunctionComponent<Props> = ({ onLoad, componentViewer,
|
|||||||
const unregisterDesktopObserver = application
|
const unregisterDesktopObserver = application
|
||||||
.getDesktopService()
|
.getDesktopService()
|
||||||
?.registerUpdateObserver((updatedComponent: ComponentInterface) => {
|
?.registerUpdateObserver((updatedComponent: ComponentInterface) => {
|
||||||
if (updatedComponent.uuid === uiFeature.uniqueIdentifier) {
|
if (updatedComponent.uuid === uiFeature.uniqueIdentifier.value) {
|
||||||
requestReload?.(componentViewer)
|
requestReload?.(componentViewer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
NewNoteTitleFormat,
|
NewNoteTitleFormat,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
EditorIdentifier,
|
|
||||||
TagPreferences,
|
TagPreferences,
|
||||||
isSmartView,
|
isSmartView,
|
||||||
isSystemView,
|
isSystemView,
|
||||||
SystemViewId,
|
SystemViewId,
|
||||||
PrefDefaults,
|
PrefDefaults,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
|
Uuid,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
@@ -17,6 +17,11 @@ import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
|||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import { AnyTag } from '@/Controllers/Navigation/AnyTagType'
|
import { AnyTag } from '@/Controllers/Navigation/AnyTagType'
|
||||||
import { PreferenceMode } from './PreferenceMode'
|
import { PreferenceMode } from './PreferenceMode'
|
||||||
|
import { EditorOption, getDropdownItemsForAllEditors } from '@/Utils/DropdownItemsForEditors'
|
||||||
|
import { classNames } from '@standardnotes/utils'
|
||||||
|
import { NoteTitleFormatOptions } from './NoteTitleFormatOptions'
|
||||||
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat'
|
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat'
|
||||||
import dayjsUTC from 'dayjs/plugin/utc'
|
import dayjsUTC from 'dayjs/plugin/utc'
|
||||||
@@ -24,11 +29,6 @@ import dayjsTimezone from 'dayjs/plugin/timezone'
|
|||||||
dayjs.extend(dayjsAdvancedFormat)
|
dayjs.extend(dayjsAdvancedFormat)
|
||||||
dayjs.extend(dayjsUTC)
|
dayjs.extend(dayjsUTC)
|
||||||
dayjs.extend(dayjsTimezone)
|
dayjs.extend(dayjsTimezone)
|
||||||
import { EditorOption, getDropdownItemsForAllEditors } from '@/Utils/DropdownItemsForEditors'
|
|
||||||
import { classNames } from '@standardnotes/utils'
|
|
||||||
import { NoteTitleFormatOptions } from './NoteTitleFormatOptions'
|
|
||||||
|
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
|
||||||
|
|
||||||
const PrefChangeDebounceTimeInMs = 25
|
const PrefChangeDebounceTimeInMs = 25
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ const NewNotePreferences: FunctionComponent<Props> = ({
|
|||||||
: selectedTag.preferences
|
: selectedTag.preferences
|
||||||
|
|
||||||
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
|
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
|
||||||
const [defaultEditorIdentifier, setDefaultEditorIdentifier] = useState<EditorIdentifier>(
|
const [defaultEditorIdentifier, setDefaultEditorIdentifier] = useState<string>(
|
||||||
FeatureIdentifier.PlainEditor,
|
NativeFeatureIdentifier.TYPES.PlainEditor,
|
||||||
)
|
)
|
||||||
const [newNoteTitleFormat, setNewNoteTitleFormat] = useState<NewNoteTitleFormat>(
|
const [newNoteTitleFormat, setNewNoteTitleFormat] = useState<NewNoteTitleFormat>(
|
||||||
NewNoteTitleFormat.CurrentDateAndTime,
|
NewNoteTitleFormat.CurrentDateAndTime,
|
||||||
@@ -121,14 +121,19 @@ const NewNotePreferences: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
const selectEditorForNewNoteDefault = useCallback(
|
const selectEditorForNewNoteDefault = useCallback(
|
||||||
(value: EditorOption['value']) => {
|
(value: EditorOption['value']) => {
|
||||||
if (application.features.getFeatureStatus(value) !== FeatureStatus.Entitled) {
|
const uuid = Uuid.create(value)
|
||||||
|
const feature = NativeFeatureIdentifier.create(value)
|
||||||
|
if (
|
||||||
|
application.features.getFeatureStatus(!uuid.isFailed() ? uuid.getValue() : feature.getValue()) !==
|
||||||
|
FeatureStatus.Entitled
|
||||||
|
) {
|
||||||
const editorItem = editorItems.find((item) => item.value === value)
|
const editorItem = editorItems.find((item) => item.value === value)
|
||||||
if (editorItem) {
|
if (editorItem) {
|
||||||
premiumModal.activate(editorItem.label)
|
premiumModal.activate(editorItem.label)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setDefaultEditorIdentifier(value as FeatureIdentifier)
|
setDefaultEditorIdentifier(value)
|
||||||
|
|
||||||
if (mode === 'global') {
|
if (mode === 'global') {
|
||||||
void application.setPreference(PrefKey.DefaultEditorIdentifier, value)
|
void application.setPreference(PrefKey.DefaultEditorIdentifier, value)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import { ContentType } from '@standardnotes/domain-core'
|
import { ContentType } from '@standardnotes/domain-core'
|
||||||
import {
|
import {
|
||||||
MutatorService,
|
|
||||||
SNComponentManager,
|
SNComponentManager,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNTag,
|
SNTag,
|
||||||
@@ -11,7 +10,7 @@ import {
|
|||||||
ItemManagerInterface,
|
ItemManagerInterface,
|
||||||
MutatorClientInterface,
|
MutatorClientInterface,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { NoteViewController } from './NoteViewController'
|
import { NoteViewController } from './NoteViewController'
|
||||||
|
|
||||||
describe('note view controller', () => {
|
describe('note view controller', () => {
|
||||||
@@ -40,7 +39,9 @@ describe('note view controller', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should create notes with plaintext note type', async () => {
|
it('should create notes with plaintext note type', async () => {
|
||||||
application.componentManager.getDefaultEditorIdentifier = jest.fn().mockReturnValue(FeatureIdentifier.PlainEditor)
|
application.componentManager.getDefaultEditorIdentifier = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||||
|
|
||||||
const controller = new NoteViewController(application)
|
const controller = new NoteViewController(application)
|
||||||
await controller.initialize()
|
await controller.initialize()
|
||||||
@@ -55,13 +56,13 @@ describe('note view controller', () => {
|
|||||||
it('should create notes with markdown note type', async () => {
|
it('should create notes with markdown note type', async () => {
|
||||||
application.items.getDisplayableComponents = jest.fn().mockReturnValue([
|
application.items.getDisplayableComponents = jest.fn().mockReturnValue([
|
||||||
{
|
{
|
||||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||||
} as SNComponent,
|
} as SNComponent,
|
||||||
])
|
])
|
||||||
|
|
||||||
application.componentManager.getDefaultEditorIdentifier = jest
|
application.componentManager.getDefaultEditorIdentifier = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue(FeatureIdentifier.MarkdownProEditor)
|
.mockReturnValue(NativeFeatureIdentifier.TYPES.MarkdownProEditor)
|
||||||
|
|
||||||
const controller = new NoteViewController(application)
|
const controller = new NoteViewController(application)
|
||||||
await controller.initialize()
|
await controller.initialize()
|
||||||
@@ -74,7 +75,9 @@ describe('note view controller', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should add tag to note if default tag is set', async () => {
|
it('should add tag to note if default tag is set', async () => {
|
||||||
application.componentManager.getDefaultEditorIdentifier = jest.fn().mockReturnValue(FeatureIdentifier.PlainEditor)
|
application.componentManager.getDefaultEditorIdentifier = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||||
|
|
||||||
const tag = {
|
const tag = {
|
||||||
uuid: 'tag-uuid',
|
uuid: 'tag-uuid',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ElementIds } from '@/Constants/ElementIDs'
|
|||||||
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
|
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
|
||||||
import { log, LoggingDomain } from '@/Logging'
|
import { log, LoggingDomain } from '@/Logging'
|
||||||
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
|
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
|
||||||
import { classNames, pluralize } from '@standardnotes/utils'
|
import { classNames, compareArrayReferences, pluralize } from '@standardnotes/utils'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
EditorLineWidth,
|
EditorLineWidth,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
isUIFeatureAnIframeFeature,
|
isUIFeatureAnIframeFeature,
|
||||||
isPayloadSourceInternalChange,
|
|
||||||
isPayloadSourceRetrieved,
|
isPayloadSourceRetrieved,
|
||||||
NoteType,
|
NoteType,
|
||||||
PayloadEmitSource,
|
PayloadEmitSource,
|
||||||
@@ -94,7 +93,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
onEditorComponentLoad?: () => void
|
onEditorComponentLoad?: () => void
|
||||||
|
|
||||||
private removeTrashKeyObserver?: () => void
|
private removeTrashKeyObserver?: () => void
|
||||||
private removeComponentStreamObserver?: () => void
|
|
||||||
private removeNoteStreamObserver?: () => void
|
private removeNoteStreamObserver?: () => void
|
||||||
private removeComponentManagerObserver?: () => void
|
private removeComponentManagerObserver?: () => void
|
||||||
private removeInnerNoteObserver?: () => void
|
private removeInnerNoteObserver?: () => void
|
||||||
@@ -144,9 +142,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
super.deinit()
|
super.deinit()
|
||||||
;(this.controller as unknown) = undefined
|
;(this.controller as unknown) = undefined
|
||||||
|
|
||||||
this.removeComponentStreamObserver?.()
|
|
||||||
;(this.removeComponentStreamObserver as unknown) = undefined
|
|
||||||
|
|
||||||
this.removeNoteStreamObserver?.()
|
this.removeNoteStreamObserver?.()
|
||||||
;(this.removeNoteStreamObserver as unknown) = undefined
|
;(this.removeNoteStreamObserver as unknown) = undefined
|
||||||
|
|
||||||
@@ -187,14 +182,19 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override shouldComponentUpdate(_nextProps: Readonly<NoteViewProps>, nextState: Readonly<State>): boolean {
|
override shouldComponentUpdate(_nextProps: Readonly<NoteViewProps>, nextState: Readonly<State>): boolean {
|
||||||
const complexObjects: (keyof State)[] = ['availableStackComponents', 'stackComponentViewers']
|
|
||||||
for (const key of Object.keys(nextState) as (keyof State)[]) {
|
for (const key of Object.keys(nextState) as (keyof State)[]) {
|
||||||
if (complexObjects.includes(key)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const prevValue = this.state[key]
|
const prevValue = this.state[key]
|
||||||
const nextValue = nextState[key]
|
const nextValue = nextState[key]
|
||||||
|
|
||||||
|
if (Array.isArray(prevValue) && Array.isArray(nextValue)) {
|
||||||
|
const areEqual = compareArrayReferences<unknown>(prevValue, nextValue)
|
||||||
|
if (!areEqual) {
|
||||||
|
log(LoggingDomain.NoteView, 'Rendering due to array state change', key, prevValue, nextValue)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if (prevValue !== nextValue) {
|
if (prevValue !== nextValue) {
|
||||||
log(LoggingDomain.NoteView, 'Rendering due to state change', key, prevValue, nextValue)
|
log(LoggingDomain.NoteView, 'Rendering due to state change', key, prevValue, nextValue)
|
||||||
return true
|
return true
|
||||||
@@ -340,7 +340,8 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
switch (eventName) {
|
switch (eventName) {
|
||||||
case ApplicationEvent.PreferencesChanged:
|
case ApplicationEvent.PreferencesChanged:
|
||||||
this.reloadPreferences().catch(console.error)
|
void this.reloadPreferences()
|
||||||
|
void this.reloadStackComponents()
|
||||||
break
|
break
|
||||||
case ApplicationEvent.HighLatencySync:
|
case ApplicationEvent.HighLatencySync:
|
||||||
this.setState({ syncTakingTooLong: true })
|
this.setState({ syncTakingTooLong: true })
|
||||||
@@ -428,23 +429,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamItems() {
|
streamItems() {
|
||||||
this.removeComponentStreamObserver = this.application.streamItems(
|
|
||||||
ContentType.TYPES.Component,
|
|
||||||
async ({ source }) => {
|
|
||||||
log(LoggingDomain.NoteView, 'On component stream observer', PayloadEmitSource[source])
|
|
||||||
if (isPayloadSourceInternalChange(source) || source === PayloadEmitSource.InitialObserverRegistrationPush) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.note) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.reloadStackComponents()
|
|
||||||
this.debounceReloadEditorComponent()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
this.removeNoteStreamObserver = this.application.streamItems<SNNote>(ContentType.TYPES.Note, async () => {
|
this.removeNoteStreamObserver = this.application.streamItems<SNNote>(ContentType.TYPES.Note, async () => {
|
||||||
if (!this.note) {
|
if (!this.note) {
|
||||||
return
|
return
|
||||||
@@ -740,25 +724,22 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
async reloadStackComponents() {
|
async reloadStackComponents() {
|
||||||
log(LoggingDomain.NoteView, 'Reload stack components')
|
log(LoggingDomain.NoteView, 'Reload stack components')
|
||||||
const stackComponents = sortAlphabetically(
|
const enabledComponents = sortAlphabetically(
|
||||||
this.application.componentManager
|
this.application.componentManager
|
||||||
.thirdPartyComponentsForArea(ComponentArea.EditorStack)
|
.thirdPartyComponentsForArea(ComponentArea.EditorStack)
|
||||||
.filter((component) => this.application.componentManager.isComponentActive(component)),
|
.filter((component) => this.application.componentManager.isComponentActive(component)),
|
||||||
)
|
)
|
||||||
const enabledComponents = stackComponents.filter((component) => {
|
|
||||||
return component.isExplicitlyEnabledForItem(this.note.uuid)
|
|
||||||
})
|
|
||||||
|
|
||||||
const needsNewViewer = enabledComponents.filter((component) => {
|
const needsNewViewer = enabledComponents.filter((component) => {
|
||||||
const hasExistingViewer = this.state.stackComponentViewers.find(
|
const hasExistingViewer = this.state.stackComponentViewers.find(
|
||||||
(viewer) => viewer.componentUniqueIdentifier === component.uuid,
|
(viewer) => viewer.componentUniqueIdentifier.value === component.uuid,
|
||||||
)
|
)
|
||||||
return !hasExistingViewer
|
return !hasExistingViewer
|
||||||
})
|
})
|
||||||
|
|
||||||
const needsDestroyViewer = this.state.stackComponentViewers.filter((viewer) => {
|
const needsDestroyViewer = this.state.stackComponentViewers.filter((viewer) => {
|
||||||
const viewerComponentExistsInEnabledComponents = enabledComponents.find((component) => {
|
const viewerComponentExistsInEnabledComponents = enabledComponents.find((component) => {
|
||||||
return component.uuid === viewer.componentUniqueIdentifier
|
return component.uuid === viewer.componentUniqueIdentifier.value
|
||||||
})
|
})
|
||||||
return !viewerComponentExistsInEnabledComponents
|
return !viewerComponentExistsInEnabledComponents
|
||||||
})
|
})
|
||||||
@@ -779,13 +760,15 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
this.application.componentManager.destroyComponentViewer(viewer)
|
this.application.componentManager.destroyComponentViewer(viewer)
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
availableStackComponents: stackComponents,
|
availableStackComponents: enabledComponents,
|
||||||
stackComponentViewers: newViewers,
|
stackComponentViewers: newViewers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
stackComponentExpanded = (component: ComponentInterface): boolean => {
|
stackComponentExpanded = (component: ComponentInterface): boolean => {
|
||||||
return !!this.state.stackComponentViewers.find((viewer) => viewer.componentUniqueIdentifier === component.uuid)
|
return !!this.state.stackComponentViewers.find(
|
||||||
|
(viewer) => viewer.componentUniqueIdentifier.value === component.uuid,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleStackComponent = async (component: ComponentInterface) => {
|
toggleStackComponent = async (component: ComponentInterface) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
MuteMarketingEmailsOption,
|
MuteMarketingEmailsOption,
|
||||||
MuteSignInEmailsOption,
|
MuteSignInEmailsOption,
|
||||||
@@ -28,7 +28,9 @@ const Email: FunctionComponent<Props> = ({ application }: Props) => {
|
|||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
const isMuteSignInEmailsFeatureAvailable =
|
const isMuteSignInEmailsFeatureAvailable =
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.SignInAlerts) === FeatureStatus.Entitled
|
application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SignInAlerts).getValue(),
|
||||||
|
) === FeatureStatus.Entitled
|
||||||
|
|
||||||
const updateSetting = async (settingName: SettingName, payload: string): Promise<boolean> => {
|
const updateSetting = async (settingName: SettingName, payload: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeatureStatus, FeatureIdentifier } from '@standardnotes/snjs'
|
import { FeatureStatus, NativeFeatureIdentifier } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useState } from 'react'
|
import { FunctionComponent, useState } from 'react'
|
||||||
|
|
||||||
@@ -29,8 +29,9 @@ const SubscriptionSharing: FunctionComponent<Props> = ({ application, viewContro
|
|||||||
const isReadOnlySession = application.sessions.isCurrentSessionReadOnly()
|
const isReadOnlySession = application.sessions.isCurrentSessionReadOnly()
|
||||||
|
|
||||||
const isSubscriptionSharingFeatureAvailable =
|
const isSubscriptionSharingFeatureAvailable =
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.SubscriptionSharing) === FeatureStatus.Entitled &&
|
application.features.getFeatureStatus(
|
||||||
!isReadOnlySession
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SubscriptionSharing).getValue(),
|
||||||
|
) === FeatureStatus.Entitled && !isReadOnlySession
|
||||||
|
|
||||||
const closeInviteDialog = () => setIsInviteDialogOpen(false)
|
const closeInviteDialog = () => setIsInviteDialogOpen(false)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
|||||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||||
import Switch from '@/Components/Switch/Switch'
|
import Switch from '@/Components/Switch/Switch'
|
||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import { FeatureIdentifier, PrefKey, FeatureStatus, naturalSort, PrefDefaults } from '@standardnotes/snjs'
|
import { PrefKey, FeatureStatus, naturalSort, PrefDefaults } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useEffect, useState } from 'react'
|
import { FunctionComponent, useEffect, useState } from 'react'
|
||||||
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||||
@@ -50,7 +50,7 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
label: theme.displayName as string,
|
label: theme.displayName as string,
|
||||||
value: theme.featureIdentifier,
|
value: theme.featureIdentifier,
|
||||||
icon:
|
icon:
|
||||||
application.features.getFeatureStatus(theme.featureIdentifier) !== FeatureStatus.Entitled
|
application.features.getFeatureStatus(theme.uniqueIdentifier) !== FeatureStatus.Entitled
|
||||||
? PremiumFeatureIconName
|
? PremiumFeatureIconName
|
||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
@@ -72,14 +72,10 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
const toggleUseDeviceSettings = () => {
|
const toggleUseDeviceSettings = () => {
|
||||||
application.setPreference(PrefKey.UseSystemColorScheme, !useDeviceSettings).catch(console.error)
|
application.setPreference(PrefKey.UseSystemColorScheme, !useDeviceSettings).catch(console.error)
|
||||||
if (!application.getPreference(PrefKey.AutoLightThemeIdentifier)) {
|
if (!application.getPreference(PrefKey.AutoLightThemeIdentifier)) {
|
||||||
application
|
application.setPreference(PrefKey.AutoLightThemeIdentifier, autoLightTheme).catch(console.error)
|
||||||
.setPreference(PrefKey.AutoLightThemeIdentifier, autoLightTheme as FeatureIdentifier)
|
|
||||||
.catch(console.error)
|
|
||||||
}
|
}
|
||||||
if (!application.getPreference(PrefKey.AutoDarkThemeIdentifier)) {
|
if (!application.getPreference(PrefKey.AutoDarkThemeIdentifier)) {
|
||||||
application
|
application.setPreference(PrefKey.AutoDarkThemeIdentifier, autoDarkTheme).catch(console.error)
|
||||||
.setPreference(PrefKey.AutoDarkThemeIdentifier, autoDarkTheme as FeatureIdentifier)
|
|
||||||
.catch(console.error)
|
|
||||||
}
|
}
|
||||||
setUseDeviceSettings(!useDeviceSettings)
|
setUseDeviceSettings(!useDeviceSettings)
|
||||||
}
|
}
|
||||||
@@ -90,7 +86,7 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
premiumModal.activate(`${item.label} theme`)
|
premiumModal.activate(`${item.label} theme`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
application.setPreference(PrefKey.AutoLightThemeIdentifier, value as FeatureIdentifier).catch(console.error)
|
application.setPreference(PrefKey.AutoLightThemeIdentifier, value).catch(console.error)
|
||||||
setAutoLightTheme(value)
|
setAutoLightTheme(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +96,7 @@ const Appearance: FunctionComponent<Props> = ({ application }) => {
|
|||||||
premiumModal.activate(`${item.label} theme`)
|
premiumModal.activate(`${item.label} theme`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
application.setPreference(PrefKey.AutoDarkThemeIdentifier, value as FeatureIdentifier).catch(console.error)
|
application.setPreference(PrefKey.AutoDarkThemeIdentifier, value).catch(console.error)
|
||||||
setAutoDarkTheme(value)
|
setAutoDarkTheme(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Cont
|
|||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
FindNativeFeature,
|
FindNativeFeature,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
@@ -17,7 +17,7 @@ import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
|||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
|
|
||||||
type ExperimentalFeatureItem = {
|
type ExperimentalFeatureItem = {
|
||||||
identifier: FeatureIdentifier
|
identifier: string
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
isEnabled: boolean
|
isEnabled: boolean
|
||||||
@@ -55,7 +55,9 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
name: feature?.name ?? featureIdentifier,
|
name: feature?.name ?? featureIdentifier,
|
||||||
description: feature?.description ?? '',
|
description: feature?.description ?? '',
|
||||||
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
|
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
|
||||||
isEntitled: application.features.getFeatureStatus(featureIdentifier) === FeatureStatus.Entitled,
|
isEntitled:
|
||||||
|
application.features.getFeatureStatus(NativeFeatureIdentifier.create(featureIdentifier).getValue()) ===
|
||||||
|
FeatureStatus.Entitled,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setExperimentalFeatures(experimentalFeatures)
|
setExperimentalFeatures(experimentalFeatures)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
import { NativeFeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
||||||
|
|
||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||||
@@ -24,8 +24,9 @@ const Security: FunctionComponent<SecurityProps> = (props) => {
|
|||||||
const isNativeMobileWeb = props.application.isNativeMobileWeb()
|
const isNativeMobileWeb = props.application.isNativeMobileWeb()
|
||||||
|
|
||||||
const isU2FFeatureAvailable =
|
const isU2FFeatureAvailable =
|
||||||
props.application.features.getFeatureStatus(FeatureIdentifier.UniversalSecondFactor) === FeatureStatus.Entitled &&
|
props.application.features.getFeatureStatus(
|
||||||
props.userProvider.getUser() !== undefined
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.UniversalSecondFactor).getValue(),
|
||||||
|
) === FeatureStatus.Entitled && props.userProvider.getUser() !== undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesPane>
|
<PreferencesPane>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
ComponentInterface,
|
ComponentInterface,
|
||||||
UIFeature,
|
UIFeature,
|
||||||
ContentType,
|
ContentType,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
PreferencesServiceEvent,
|
PreferencesServiceEvent,
|
||||||
ThemeFeatureDescription,
|
ThemeFeatureDescription,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
@@ -54,7 +54,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ quickSettingsMenuCont
|
|||||||
(component) =>
|
(component) =>
|
||||||
!component.isTheme() &&
|
!component.isTheme() &&
|
||||||
[ComponentArea.EditorStack].includes(component.area) &&
|
[ComponentArea.EditorStack].includes(component.area) &&
|
||||||
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
|
component.identifier !== NativeFeatureIdentifier.TYPES.DeprecatedFoldersComponent,
|
||||||
)
|
)
|
||||||
|
|
||||||
setEditorStackComponents(toggleableComponents)
|
setEditorStackComponents(toggleableComponents)
|
||||||
@@ -100,7 +100,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ quickSettingsMenuCont
|
|||||||
|
|
||||||
const toggleEditorStackComponent = useCallback(
|
const toggleEditorStackComponent = useCallback(
|
||||||
(component: ComponentInterface) => {
|
(component: ComponentInterface) => {
|
||||||
application.componentManager.toggleComponent(component).catch(console.error)
|
void application.componentManager.toggleComponent(component)
|
||||||
},
|
},
|
||||||
[application],
|
[application],
|
||||||
)
|
)
|
||||||
@@ -141,7 +141,7 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ quickSettingsMenuCont
|
|||||||
Default
|
Default
|
||||||
</MenuRadioButtonItem>
|
</MenuRadioButtonItem>
|
||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<ThemesMenuButton uiFeature={theme} key={theme.uniqueIdentifier} />
|
<ThemesMenuButton uiFeature={theme} key={theme.uniqueIdentifier.value} />
|
||||||
))}
|
))}
|
||||||
<HorizontalSeparator classes="my-2" />
|
<HorizontalSeparator classes="my-2" />
|
||||||
<FocusModeSwitch
|
<FocusModeSwitch
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UIFeature, FeatureIdentifier, FeatureStatus, ThemeFeatureDescription } from '@standardnotes/snjs'
|
import { UIFeature, NativeFeatureIdentifier, FeatureStatus, ThemeFeatureDescription } from '@standardnotes/snjs'
|
||||||
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||||
@@ -26,8 +26,8 @@ const ThemesMenuButton: FunctionComponent<Props> = ({ uiFeature }) => {
|
|||||||
[application, uiFeature.featureIdentifier],
|
[application, uiFeature.featureIdentifier],
|
||||||
)
|
)
|
||||||
const isEntitledToTheme = useMemo(
|
const isEntitledToTheme = useMemo(
|
||||||
() => application.features.getFeatureStatus(uiFeature.featureIdentifier) === FeatureStatus.Entitled,
|
() => application.features.getFeatureStatus(uiFeature.uniqueIdentifier) === FeatureStatus.Entitled,
|
||||||
[application, uiFeature.featureIdentifier],
|
[application, uiFeature.uniqueIdentifier],
|
||||||
)
|
)
|
||||||
const canActivateTheme = useMemo(() => isEntitledToTheme || isThirdPartyTheme, [isEntitledToTheme, isThirdPartyTheme])
|
const canActivateTheme = useMemo(() => isEntitledToTheme || isThirdPartyTheme, [isEntitledToTheme, isThirdPartyTheme])
|
||||||
|
|
||||||
@@ -55,10 +55,10 @@ const ThemesMenuButton: FunctionComponent<Props> = ({ uiFeature }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const isMobile = application.isNativeMobileWeb() || isMobileScreen()
|
const isMobile = application.isNativeMobileWeb() || isMobileScreen()
|
||||||
const shouldHideButton = uiFeature.featureIdentifier === FeatureIdentifier.DynamicTheme && isMobile
|
const shouldHideButton = uiFeature.featureIdentifier === NativeFeatureIdentifier.TYPES.DynamicTheme && isMobile
|
||||||
|
|
||||||
const darkThemeShortcut = useMemo(() => {
|
const darkThemeShortcut = useMemo(() => {
|
||||||
if (uiFeature.featureIdentifier === FeatureIdentifier.DarkTheme) {
|
if (uiFeature.featureIdentifier === NativeFeatureIdentifier.TYPES.DarkTheme) {
|
||||||
return commandService.keyboardShortcutForCommand(TOGGLE_DARK_MODE_COMMAND)
|
return commandService.keyboardShortcutForCommand(TOGGLE_DARK_MODE_COMMAND)
|
||||||
}
|
}
|
||||||
}, [commandService, uiFeature.featureIdentifier])
|
}, [commandService, uiFeature.featureIdentifier])
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/features'
|
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||||
import { NoteType, PredicateCompoundOperator, PredicateJsonForm } from '@standardnotes/snjs'
|
import { NoteType, PredicateCompoundOperator, PredicateJsonForm } from '@standardnotes/snjs'
|
||||||
import { makeObservable, observable, action } from 'mobx'
|
import { makeObservable, observable, action } from 'mobx'
|
||||||
import { PredicateKeypath, PredicateKeypathTypes } from './PredicateKeypaths'
|
import { PredicateKeypath, PredicateKeypathTypes } from './PredicateKeypaths'
|
||||||
@@ -59,7 +59,7 @@ export class CompoundPredicateBuilderController {
|
|||||||
this.setPredicate(index, { value: Object.values(NoteType)[0] })
|
this.setPredicate(index, { value: Object.values(NoteType)[0] })
|
||||||
break
|
break
|
||||||
case 'editorIdentifier':
|
case 'editorIdentifier':
|
||||||
this.setPredicate(index, { value: FeatureIdentifier.PlainEditor })
|
this.setPredicate(index, { value: NativeFeatureIdentifier.TYPES.PlainEditor })
|
||||||
break
|
break
|
||||||
case 'date':
|
case 'date':
|
||||||
this.setPredicate(index, { value: '1.days.ago' })
|
this.setPredicate(index, { value: '1.days.ago' })
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
isPayloadSourceRetrieved,
|
isPayloadSourceRetrieved,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
PrefDefaults,
|
PrefDefaults,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
GetSuperNoteFeature,
|
GetSuperNoteFeature,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
@@ -76,7 +76,12 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFeatureStatus(
|
setFeatureStatus(
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.SuperEditor, { inContextOfItem: note.current }),
|
application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
|
||||||
|
{
|
||||||
|
inContextOfItem: note.current,
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}, [application.features])
|
}, [application.features])
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { PremiumFeatureModalType } from '@/Components/PremiumFeaturesModal/Premi
|
|||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
FeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
FeatureStatus,
|
FeatureStatus,
|
||||||
InternalEventBusInterface,
|
InternalEventBusInterface,
|
||||||
InternalEventInterface,
|
InternalEventInterface,
|
||||||
@@ -100,19 +100,25 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isEntitledToFiles(): boolean {
|
private isEntitledToFiles(): boolean {
|
||||||
const status = this.application.features.getFeatureStatus(FeatureIdentifier.Files)
|
const status = this.application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.Files).getValue(),
|
||||||
|
)
|
||||||
|
|
||||||
return status === FeatureStatus.Entitled
|
return status === FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEntitledToFolders(): boolean {
|
private isEntitledToFolders(): boolean {
|
||||||
const status = this.application.features.getFeatureStatus(FeatureIdentifier.TagNesting)
|
const status = this.application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TagNesting).getValue(),
|
||||||
|
)
|
||||||
|
|
||||||
return status === FeatureStatus.Entitled
|
return status === FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEntitledToSmartViews(): boolean {
|
private isEntitledToSmartViews(): boolean {
|
||||||
const status = this.application.features.getFeatureStatus(FeatureIdentifier.SmartFilters)
|
const status = this.application.features.getFeatureStatus(
|
||||||
|
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SmartFilters).getValue(),
|
||||||
|
)
|
||||||
|
|
||||||
return status === FeatureStatus.Entitled
|
return status === FeatureStatus.Entitled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { FeatureIdentifier } from '@standardnotes/snjs'
|
import {
|
||||||
import { ComponentArea, FindNativeFeature, GetIframeAndNativeEditors } from '@standardnotes/features'
|
ComponentArea,
|
||||||
|
FindNativeFeature,
|
||||||
|
GetIframeAndNativeEditors,
|
||||||
|
NativeFeatureIdentifier,
|
||||||
|
} from '@standardnotes/features'
|
||||||
import { getIconAndTintForNoteType } from './Items/Icons/getIconAndTintForNoteType'
|
import { getIconAndTintForNoteType } from './Items/Icons/getIconAndTintForNoteType'
|
||||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
export type EditorOption = DropdownItem & {
|
export type EditorOption = DropdownItem & {
|
||||||
value: FeatureIdentifier
|
value: string
|
||||||
isLabs?: boolean
|
isLabs?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +23,7 @@ export function getDropdownItemsForAllEditors(application: WebApplicationInterfa
|
|||||||
return {
|
return {
|
||||||
label: editor.name,
|
label: editor.name,
|
||||||
value: editor.identifier,
|
value: editor.identifier,
|
||||||
|
id: NativeFeatureIdentifier.create(editor.identifier).getValue(),
|
||||||
...(iconType ? { icon: iconType } : null),
|
...(iconType ? { icon: iconType } : null),
|
||||||
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
||||||
}
|
}
|
||||||
@@ -30,12 +35,11 @@ export function getDropdownItemsForAllEditors(application: WebApplicationInterfa
|
|||||||
.thirdPartyComponentsForArea(ComponentArea.Editor)
|
.thirdPartyComponentsForArea(ComponentArea.Editor)
|
||||||
.filter((component) => FindNativeFeature(component.identifier) === undefined)
|
.filter((component) => FindNativeFeature(component.identifier) === undefined)
|
||||||
.map((editor): EditorOption => {
|
.map((editor): EditorOption => {
|
||||||
const identifier = editor.package_info.identifier
|
|
||||||
const [iconType, tint] = getIconAndTintForNoteType(editor.noteType)
|
const [iconType, tint] = getIconAndTintForNoteType(editor.noteType)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: editor.displayName,
|
label: editor.displayName,
|
||||||
value: identifier,
|
value: editor.uuid,
|
||||||
...(iconType ? { icon: iconType } : null),
|
...(iconType ? { icon: iconType } : null),
|
||||||
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { UIFeature, FeatureIdentifier, ThemeFeatureDescription } from '@standardnotes/snjs'
|
import { UIFeature, NativeFeatureIdentifier, ThemeFeatureDescription } from '@standardnotes/snjs'
|
||||||
|
|
||||||
const isDarkModeTheme = (theme: UIFeature<ThemeFeatureDescription>) =>
|
const isDarkModeTheme = (theme: UIFeature<ThemeFeatureDescription>) =>
|
||||||
theme.featureIdentifier === FeatureIdentifier.DarkTheme
|
theme.featureIdentifier === NativeFeatureIdentifier.TYPES.DarkTheme
|
||||||
|
|
||||||
export const sortThemes = (a: UIFeature<ThemeFeatureDescription>, b: UIFeature<ThemeFeatureDescription>) => {
|
export const sortThemes = (a: UIFeature<ThemeFeatureDescription>, b: UIFeature<ThemeFeatureDescription>) => {
|
||||||
const aIsLayerable = a.layerable
|
const aIsLayerable = a.layerable
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
GetSuperNoteFeature,
|
GetSuperNoteFeature,
|
||||||
UIFeature,
|
UIFeature,
|
||||||
IframeComponentFeatureDescription,
|
IframeComponentFeatureDescription,
|
||||||
|
Uuid,
|
||||||
|
NativeFeatureIdentifier,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
||||||
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
||||||
@@ -29,7 +31,9 @@ const insertNativeEditorsInMap = (map: NoteTypeToEditorRowsMap, application: Web
|
|||||||
|
|
||||||
const noteType = editorFeature.note_type
|
const noteType = editorFeature.note_type
|
||||||
map[noteType].push({
|
map[noteType].push({
|
||||||
isEntitled: application.features.getFeatureStatus(editorFeature.identifier) === FeatureStatus.Entitled,
|
isEntitled:
|
||||||
|
application.features.getFeatureStatus(NativeFeatureIdentifier.create(editorFeature.identifier).getValue()) ===
|
||||||
|
FeatureStatus.Entitled,
|
||||||
uiFeature: new UIFeature(editorFeature),
|
uiFeature: new UIFeature(editorFeature),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -52,7 +56,7 @@ const insertInstalledComponentsInMap = (map: NoteTypeToEditorRowsMap, applicatio
|
|||||||
|
|
||||||
const editorItem: EditorMenuItem = {
|
const editorItem: EditorMenuItem = {
|
||||||
uiFeature: new UIFeature<IframeComponentFeatureDescription>(editor),
|
uiFeature: new UIFeature<IframeComponentFeatureDescription>(editor),
|
||||||
isEntitled: application.features.getFeatureStatus(editor.identifier) === FeatureStatus.Entitled,
|
isEntitled: application.features.getFeatureStatus(Uuid.create(editor.uuid).getValue()) === FeatureStatus.Entitled,
|
||||||
}
|
}
|
||||||
|
|
||||||
map[noteType].push(editorItem)
|
map[noteType].push(editorItem)
|
||||||
|
|||||||
Reference in New Issue
Block a user