refactor: native feature management (#2350)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { ComponentPermission } from '@standardnotes/features'
|
||||
import { SNComponent } from '../../Syncable/Component'
|
||||
import { ComponentInterface } from '../../Syncable/Component'
|
||||
|
||||
export type PermissionDialog = {
|
||||
component: SNComponent
|
||||
component: ComponentInterface
|
||||
permissions: ComponentPermission[]
|
||||
permissionsString: string
|
||||
actionBlock: (approved: boolean) => void
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DecryptedItemInterface } from './DecryptedItem'
|
||||
import { isDecryptedPayload, isDeletedPayload, isEncryptedPayload } from '../../Payload/Interfaces/TypeCheck'
|
||||
|
||||
export function isDecryptedItem(item: ItemInterface): item is DecryptedItemInterface {
|
||||
return isDecryptedPayload(item.payload)
|
||||
return 'payload' in item && isDecryptedPayload(item.payload)
|
||||
}
|
||||
|
||||
export function isEncryptedItem(item: ItemInterface): item is EncryptedItemInterface {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class DecryptedItemMutator<
|
||||
constructor(item: I, type: MutationType) {
|
||||
super(item, type)
|
||||
|
||||
const mutableCopy = Copy(this.immutablePayload.content)
|
||||
const mutableCopy = Copy<C>(this.immutablePayload.content)
|
||||
this.mutableContent = mutableCopy
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('component model', () => {
|
||||
package_info: {
|
||||
note_type: NoteType.Authentication,
|
||||
},
|
||||
} as ComponentContent),
|
||||
} as unknown as ComponentContent),
|
||||
...PayloadTimestampDefaults(),
|
||||
},
|
||||
PayloadSource.Constructor,
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
ComponentPermission,
|
||||
FindNativeFeature,
|
||||
NoteType,
|
||||
isEditorFeatureDescription,
|
||||
} from '@standardnotes/features'
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { ComponentContent, ComponentInterface } from './ComponentContent'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
import { ComponentInterface } from './ComponentInterface'
|
||||
import { ConflictStrategy } from '../../Abstract/Item/Types/ConflictStrategy'
|
||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||
@@ -19,12 +21,24 @@ import { Predicate } from '../../Runtime/Predicate/Predicate'
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
import { DecryptedItemInterface } from './../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ComponentPackageInfo } from './PackageInfo'
|
||||
import { isDecryptedItem } from '../../Abstract/Item'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export const isComponent = (x: ItemInterface): x is SNComponent => x.content_type === ContentType.TYPES.Component
|
||||
export function isComponent(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
export const isComponentOrTheme = (x: ItemInterface): x is SNComponent =>
|
||||
x.content_type === ContentType.TYPES.Component || x.content_type === ContentType.TYPES.Theme
|
||||
return x.content_type === ContentType.TYPES.Component
|
||||
}
|
||||
|
||||
export function isComponentOrTheme(x: ItemInterface): x is ComponentInterface {
|
||||
if (!isDecryptedItem(x as DecryptedItemInterface)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return x.content_type === ContentType.TYPES.Component || x.content_type === ContentType.TYPES.Theme
|
||||
}
|
||||
|
||||
/**
|
||||
* Components are mostly iframe based extensions that communicate with the SN parent
|
||||
@@ -32,7 +46,7 @@ export const isComponentOrTheme = (x: ItemInterface): x is SNComponent =>
|
||||
* only by its url.
|
||||
*/
|
||||
export class SNComponent extends DecryptedItem<ComponentContent> implements ComponentInterface {
|
||||
public readonly componentData: Record<string, unknown>
|
||||
public readonly legacyComponentData: Record<string, unknown>
|
||||
/** Items that have requested a component to be disabled in its context */
|
||||
public readonly disassociatedItemIds: string[]
|
||||
/** Items that have requested a component to be enabled in its context */
|
||||
@@ -46,14 +60,12 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
public readonly area: ComponentArea
|
||||
public readonly permissions: ComponentPermission[] = []
|
||||
public readonly valid_until: Date
|
||||
public readonly active: boolean
|
||||
public readonly legacyActive: boolean
|
||||
public readonly legacy_url?: string
|
||||
public readonly isMobileDefault: boolean
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<ComponentContent>) {
|
||||
super(payload)
|
||||
/** Custom data that a component can store in itself */
|
||||
this.componentData = this.payload.content.componentData || {}
|
||||
|
||||
if (payload.content.hosted_url && isValidUrl(payload.content.hosted_url)) {
|
||||
this.hosted_url = payload.content.hosted_url
|
||||
@@ -65,16 +77,16 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
this.local_url = payload.content.local_url
|
||||
|
||||
this.valid_until = new Date(payload.content.valid_until || 0)
|
||||
this.offlineOnly = payload.content.offlineOnly
|
||||
this.offlineOnly = payload.content.offlineOnly ?? false
|
||||
this.name = payload.content.name
|
||||
this.area = payload.content.area
|
||||
this.package_info = payload.content.package_info || {}
|
||||
this.permissions = payload.content.permissions || []
|
||||
this.active = payload.content.active
|
||||
this.autoupdateDisabled = payload.content.autoupdateDisabled
|
||||
this.autoupdateDisabled = payload.content.autoupdateDisabled ?? false
|
||||
this.disassociatedItemIds = payload.content.disassociatedItemIds || []
|
||||
this.associatedItemIds = payload.content.associatedItemIds || []
|
||||
this.isMobileDefault = payload.content.isMobileDefault
|
||||
this.isMobileDefault = payload.content.isMobileDefault ?? false
|
||||
|
||||
/**
|
||||
* @legacy
|
||||
* We don't want to set this.url directly, as we'd like to phase it out.
|
||||
@@ -83,6 +95,10 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
* hosted_url is the url replacement.
|
||||
*/
|
||||
this.legacy_url = !payload.content.hosted_url ? payload.content.url : undefined
|
||||
|
||||
this.legacyComponentData = this.payload.content.componentData || {}
|
||||
|
||||
this.legacyActive = payload.content.active ?? false
|
||||
}
|
||||
|
||||
/** Do not duplicate components under most circumstances. Always keep original */
|
||||
@@ -123,18 +139,6 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
return this.getAppDomainValue(AppDataField.LastSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* The key used to look up data that this component may have saved to an item.
|
||||
* This data will be stored on the item using this key.
|
||||
*/
|
||||
public getClientDataKey(): string {
|
||||
if (this.legacy_url) {
|
||||
return this.legacy_url
|
||||
} else {
|
||||
return this.uuid
|
||||
}
|
||||
}
|
||||
|
||||
public hasValidHostedUrl(): boolean {
|
||||
return (this.hosted_url || this.legacy_url) != undefined
|
||||
}
|
||||
@@ -180,7 +184,11 @@ export class SNComponent extends DecryptedItem<ComponentContent> implements Comp
|
||||
}
|
||||
|
||||
public get noteType(): NoteType {
|
||||
return this.package_info.note_type || NoteType.Unknown
|
||||
if (isEditorFeatureDescription(this.package_info)) {
|
||||
return this.package_info.note_type ?? NoteType.Unknown
|
||||
}
|
||||
|
||||
return NoteType.Unknown
|
||||
}
|
||||
|
||||
public get isDeprecated(): boolean {
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
import { ComponentArea, ComponentPermission } from '@standardnotes/features'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { ComponentPackageInfo } from './PackageInfo'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export interface ComponentInterface {
|
||||
componentData: Record<string, any>
|
||||
|
||||
export type ComponentContentSpecialized = {
|
||||
/** Items that have requested a component to be disabled in its context */
|
||||
disassociatedItemIds: string[]
|
||||
disassociatedItemIds?: string[]
|
||||
|
||||
/** Items that have requested a component to be enabled in its context */
|
||||
associatedItemIds: string[]
|
||||
associatedItemIds?: string[]
|
||||
|
||||
local_url?: string
|
||||
hosted_url?: string
|
||||
|
||||
offlineOnly?: boolean
|
||||
name: string
|
||||
autoupdateDisabled?: boolean
|
||||
package_info: ComponentPackageInfo
|
||||
area: ComponentArea
|
||||
permissions?: ComponentPermission[]
|
||||
valid_until: Date | number
|
||||
|
||||
legacy_url?: string
|
||||
isMobileDefault?: boolean
|
||||
isDeprecated?: boolean
|
||||
|
||||
/** @deprecated */
|
||||
active?: boolean
|
||||
|
||||
/** @deprecated */
|
||||
url?: string
|
||||
|
||||
offlineOnly: boolean
|
||||
name: string
|
||||
autoupdateDisabled: boolean
|
||||
package_info: ComponentPackageInfo
|
||||
area: ComponentArea
|
||||
permissions: ComponentPermission[]
|
||||
valid_until: Date | number
|
||||
active: boolean
|
||||
legacy_url?: string
|
||||
isMobileDefault: boolean
|
||||
isDeprecated: boolean
|
||||
isExplicitlyEnabledForItem(uuid: string): boolean
|
||||
/**
|
||||
* @deprecated
|
||||
* Replaced with per-note component data stored in the note's ComponentDataDomain.
|
||||
*/
|
||||
componentData?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type ComponentContent = ComponentInterface & ItemContent
|
||||
export type ComponentContent = ItemContent & ComponentContentSpecialized
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
ComponentArea,
|
||||
ComponentPermission,
|
||||
FeatureIdentifier,
|
||||
NoteType,
|
||||
ThirdPartyFeatureDescription,
|
||||
} from '@standardnotes/features'
|
||||
import { ComponentPackageInfo } from './PackageInfo'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
|
||||
export interface ComponentInterface extends DecryptedItemInterface<ComponentContent> {
|
||||
/** Items that have requested a component to be disabled in its context */
|
||||
disassociatedItemIds: string[]
|
||||
|
||||
/** Items that have requested a component to be enabled in its context */
|
||||
associatedItemIds: string[]
|
||||
|
||||
local_url?: string
|
||||
hosted_url?: string
|
||||
|
||||
offlineOnly: boolean
|
||||
name: string
|
||||
autoupdateDisabled: boolean
|
||||
package_info: ComponentPackageInfo
|
||||
area: ComponentArea
|
||||
permissions: ComponentPermission[]
|
||||
valid_until: Date
|
||||
isMobileDefault: boolean
|
||||
isDeprecated: boolean
|
||||
|
||||
isExplicitlyEnabledForItem(uuid: string): boolean
|
||||
hasValidHostedUrl(): boolean
|
||||
isTheme(): boolean
|
||||
isExplicitlyDisabledForItem(uuid: string): boolean
|
||||
legacyIsDefaultEditor(): boolean
|
||||
|
||||
get identifier(): FeatureIdentifier
|
||||
get noteType(): NoteType
|
||||
get displayName(): string
|
||||
get deprecationMessage(): string | undefined
|
||||
get thirdPartyPackageInfo(): ThirdPartyFeatureDescription
|
||||
get isExpired(): boolean
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Replaced with active preferences managed by preferences service.
|
||||
*/
|
||||
legacyActive: boolean
|
||||
|
||||
/** @deprecated */
|
||||
legacy_url?: string
|
||||
|
||||
/** @deprecated */
|
||||
url?: string
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Replaced with per-note component data stored in the note's ComponentDataDomain.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
legacyComponentData: Record<string, any>
|
||||
}
|
||||
@@ -1,23 +1,15 @@
|
||||
import { addIfUnique, removeFromArray } from '@standardnotes/utils'
|
||||
import { ComponentPermission, FeatureDescription } from '@standardnotes/features'
|
||||
import { ComponentFeatureDescription, ComponentPermission } from '@standardnotes/features'
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { ComponentContent } from './ComponentContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
|
||||
export class ComponentMutator extends DecryptedItemMutator<ComponentContent> {
|
||||
set active(active: boolean) {
|
||||
this.mutableContent.active = active
|
||||
}
|
||||
|
||||
set isMobileDefault(isMobileDefault: boolean) {
|
||||
this.mutableContent.isMobileDefault = isMobileDefault
|
||||
}
|
||||
|
||||
set componentData(componentData: Record<string, unknown>) {
|
||||
this.mutableContent.componentData = componentData
|
||||
}
|
||||
|
||||
set package_info(package_info: FeatureDescription) {
|
||||
set package_info(package_info: ComponentFeatureDescription) {
|
||||
this.mutableContent.package_info = package_info
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
AnyFeatureDescription,
|
||||
ComponentArea,
|
||||
ComponentPermission,
|
||||
EditorFeatureDescription,
|
||||
FeatureIdentifier,
|
||||
IframeComponentFeatureDescription,
|
||||
NoteType,
|
||||
ThemeDockIcon,
|
||||
UIFeatureDescriptionTypes,
|
||||
isEditorFeatureDescription,
|
||||
isIframeComponentFeatureDescription,
|
||||
isThemeFeatureDescription,
|
||||
} from '@standardnotes/features'
|
||||
import { ComponentInterface } from './ComponentInterface'
|
||||
import { isTheme } from '../Theme'
|
||||
|
||||
function isComponent(x: ComponentInterface | UIFeatureDescriptionTypes): x is ComponentInterface {
|
||||
return 'uuid' in x
|
||||
}
|
||||
|
||||
function isFeatureDescription(x: ComponentInterface | AnyFeatureDescription): x is AnyFeatureDescription {
|
||||
return !('uuid' in x)
|
||||
}
|
||||
|
||||
export function isIframeUIFeature(
|
||||
x: ComponentOrNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>,
|
||||
): x is ComponentOrNativeFeature<IframeComponentFeatureDescription> {
|
||||
return isIframeComponentFeatureDescription(x.featureDescription)
|
||||
}
|
||||
|
||||
export class ComponentOrNativeFeature<F extends UIFeatureDescriptionTypes> {
|
||||
constructor(public readonly item: ComponentInterface | F) {}
|
||||
|
||||
get isComponent(): boolean {
|
||||
return isComponent(this.item)
|
||||
}
|
||||
|
||||
get isFeatureDescription(): boolean {
|
||||
return isFeatureDescription(this.item)
|
||||
}
|
||||
|
||||
get isThemeComponent(): boolean {
|
||||
return isComponent(this.item) && isTheme(this.item)
|
||||
}
|
||||
|
||||
get asComponent(): ComponentInterface {
|
||||
if (isComponent(this.item)) {
|
||||
return this.item
|
||||
}
|
||||
|
||||
throw new Error('Cannot cast item to component')
|
||||
}
|
||||
|
||||
get asFeatureDescription(): F {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item
|
||||
}
|
||||
|
||||
throw new Error('Cannot cast item to feature description')
|
||||
}
|
||||
|
||||
get uniqueIdentifier(): string {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item.identifier
|
||||
} else {
|
||||
return this.item.uuid
|
||||
}
|
||||
}
|
||||
|
||||
get featureIdentifier(): FeatureIdentifier {
|
||||
return this.item.identifier
|
||||
}
|
||||
|
||||
get noteType(): NoteType {
|
||||
if (isFeatureDescription(this.item) && isEditorFeatureDescription(this.item)) {
|
||||
return this.item.note_type ?? NoteType.Unknown
|
||||
} else if (isComponent(this.item)) {
|
||||
return this.item.noteType
|
||||
}
|
||||
|
||||
throw new Error('Invalid component or feature description')
|
||||
}
|
||||
|
||||
get fileType(): EditorFeatureDescription['file_type'] {
|
||||
if (isFeatureDescription(this.item) && isEditorFeatureDescription(this.item)) {
|
||||
return this.item.file_type
|
||||
} else if (isComponent(this.item) && isEditorFeatureDescription(this.item.package_info)) {
|
||||
return this.item.package_info?.file_type ?? 'txt'
|
||||
}
|
||||
|
||||
throw new Error('Invalid component or feature description')
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item.name ?? ''
|
||||
} else {
|
||||
return this.item.displayName
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item.description ?? ''
|
||||
} else {
|
||||
return this.item.package_info.description ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
get deprecationMessage(): string | undefined {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item.deprecation_message
|
||||
} else {
|
||||
return this.item.deprecationMessage
|
||||
}
|
||||
}
|
||||
|
||||
get expirationDate(): Date | undefined {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item.expires_at ? new Date(this.item.expires_at) : undefined
|
||||
} else {
|
||||
return this.item.valid_until
|
||||
}
|
||||
}
|
||||
|
||||
get featureDescription(): F {
|
||||
if (isFeatureDescription(this.item)) {
|
||||
return this.item
|
||||
} else {
|
||||
return this.item.package_info as F
|
||||
}
|
||||
}
|
||||
|
||||
get acquiredPermissions(): ComponentPermission[] {
|
||||
if (isFeatureDescription(this.item) && isIframeComponentFeatureDescription(this.item)) {
|
||||
return this.item.component_permissions ?? []
|
||||
} else if (isComponent(this.item)) {
|
||||
return this.item.permissions
|
||||
}
|
||||
|
||||
throw new Error('Invalid component or feature description')
|
||||
}
|
||||
|
||||
get area(): ComponentArea {
|
||||
if ('area' in this.item) {
|
||||
return this.item.area
|
||||
}
|
||||
|
||||
return ComponentArea.Editor
|
||||
}
|
||||
|
||||
get layerable(): boolean {
|
||||
if (isComponent(this.item) && isTheme(this.item)) {
|
||||
return this.item.layerable
|
||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||
return this.asFeatureDescription.layerable ?? false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
get dockIcon(): ThemeDockIcon | undefined {
|
||||
if (isComponent(this.item) && isTheme(this.item)) {
|
||||
return this.item.package_info.dock_icon
|
||||
} else if (isThemeFeatureDescription(this.asFeatureDescription)) {
|
||||
return this.asFeatureDescription.dock_icon
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FeatureDescription, ThemeFeatureDescription } from '@standardnotes/features'
|
||||
import { ComponentFeatureDescription, ThemeFeatureDescription } from '@standardnotes/features'
|
||||
|
||||
type ThirdPartyPackageInfo = {
|
||||
version: string
|
||||
download_url?: string
|
||||
}
|
||||
|
||||
export type ComponentPackageInfo = FeatureDescription & Partial<ThirdPartyPackageInfo>
|
||||
export type ThemePackageInfo = FeatureDescription & Partial<ThirdPartyPackageInfo> & ThemeFeatureDescription
|
||||
export type ComponentPackageInfo = ComponentFeatureDescription & Partial<ThirdPartyPackageInfo>
|
||||
export type ThemePackageInfo = ThemeFeatureDescription & Partial<ThirdPartyPackageInfo> & ThemeFeatureDescription
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './Component'
|
||||
export * from './ComponentMutator'
|
||||
export * from './ComponentContent'
|
||||
export * from './ComponentInterface'
|
||||
export * from './ComponentOrNativeFeature'
|
||||
export * from './PackageInfo'
|
||||
|
||||
@@ -1,50 +1,18 @@
|
||||
import { ComponentArea } from '@standardnotes/features'
|
||||
import { SNComponent } from '../Component/Component'
|
||||
import { ConflictStrategy } from '../../Abstract/Item/Types/ConflictStrategy'
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { DecryptedItemInterface, ItemInterface } from '../../Abstract/Item'
|
||||
import { ItemInterface } from '../../Abstract/Item'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { useBoolean } from '@standardnotes/utils'
|
||||
import { ThemePackageInfo } from '../Component/PackageInfo'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { ThemeInterface } from './ThemeInterface'
|
||||
|
||||
export const isTheme = (x: ItemInterface): x is SNTheme => x.content_type === ContentType.TYPES.Theme
|
||||
export const isTheme = (x: ItemInterface): x is ThemeInterface => x.content_type === ContentType.TYPES.Theme
|
||||
|
||||
export class SNTheme extends SNComponent {
|
||||
export class SNTheme extends SNComponent implements ThemeInterface {
|
||||
public override area: ComponentArea = ComponentArea.Themes
|
||||
public declare readonly package_info: ThemePackageInfo
|
||||
|
||||
isLayerable(): boolean {
|
||||
get layerable(): boolean {
|
||||
return useBoolean(this.package_info && this.package_info.layerable, false)
|
||||
}
|
||||
|
||||
/** Do not duplicate under most circumstances. Always keep original */
|
||||
override strategyWhenConflictingWithItem(
|
||||
_item: DecryptedItemInterface,
|
||||
_previousRevision?: HistoryEntryInterface,
|
||||
): ConflictStrategy {
|
||||
return ConflictStrategy.KeepBase
|
||||
}
|
||||
|
||||
getMobileRules() {
|
||||
return (
|
||||
this.getAppDomainValue(AppDataField.MobileRules) || {
|
||||
constants: {},
|
||||
rules: {},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Same as getMobileRules but without default value. */
|
||||
hasMobileRules() {
|
||||
return this.getAppDomainValue(AppDataField.MobileRules)
|
||||
}
|
||||
|
||||
getNotAvailOnMobile() {
|
||||
return this.getAppDomainValue(AppDataField.NotAvailableOnMobile)
|
||||
}
|
||||
|
||||
isMobileActive() {
|
||||
return this.getAppDomainValue(AppDataField.MobileActive)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ComponentInterface } from '../Component'
|
||||
import { ThemePackageInfo } from '../Component/PackageInfo'
|
||||
|
||||
export interface ThemeInterface extends ComponentInterface {
|
||||
get layerable(): boolean
|
||||
readonly package_info: ThemePackageInfo
|
||||
}
|
||||
@@ -1,25 +1,4 @@
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
import { ComponentContent } from '../Component/ComponentContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
|
||||
export class ThemeMutator extends DecryptedItemMutator<ComponentContent> {
|
||||
setMobileRules(rules: unknown) {
|
||||
this.setAppDataItem(AppDataField.MobileRules, rules)
|
||||
}
|
||||
|
||||
setNotAvailOnMobile(notAvailable: boolean) {
|
||||
this.setAppDataItem(AppDataField.NotAvailableOnMobile, notAvailable)
|
||||
}
|
||||
|
||||
set local_url(local_url: string) {
|
||||
this.mutableContent.local_url = local_url
|
||||
}
|
||||
|
||||
/**
|
||||
* We must not use .active because if you set that to true, it will also
|
||||
* activate that theme on desktop/web
|
||||
*/
|
||||
setMobileActive(active: boolean) {
|
||||
this.setAppDataItem(AppDataField.MobileActive, active)
|
||||
}
|
||||
}
|
||||
export class ThemeMutator extends DecryptedItemMutator<ComponentContent> {}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './Theme'
|
||||
export * from './ThemeMutator'
|
||||
export * from './ThemeInterface'
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
|
||||
type UuidString = string
|
||||
|
||||
export type AllComponentPreferences = Record<FeatureIdentifier | UuidString, ComponentPreferencesEntry>
|
||||
|
||||
export type ComponentPreferencesEntry = Record<string, unknown>
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum EditorFontSize {
|
||||
ExtraSmall = 'ExtraSmall',
|
||||
Small = 'Small',
|
||||
Normal = 'Normal',
|
||||
Medium = 'Medium',
|
||||
Large = 'Large',
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum EditorLineHeight {
|
||||
None = 'None',
|
||||
Tight = 'Tight',
|
||||
Snug = 'Snug',
|
||||
Normal = 'Normal',
|
||||
Relaxed = 'Relaxed',
|
||||
Loose = 'Loose',
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum EditorLineWidth {
|
||||
Narrow = 'Narrow',
|
||||
Wide = 'Wide',
|
||||
Dynamic = 'Dynamic',
|
||||
FullWidth = 'FullWidth',
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum NewNoteTitleFormat {
|
||||
CurrentDateAndTime = 'CurrentDateAndTime',
|
||||
CurrentNoteCount = 'CurrentNoteCount',
|
||||
CustomFormat = 'CustomFormat',
|
||||
Empty = 'Empty',
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
import { CollectionSort } from '../../Runtime/Collection/CollectionSort'
|
||||
import { EditorFontSize } from './EditorFontSize'
|
||||
import { EditorLineHeight } from './EditorLineHeight'
|
||||
import { EditorLineWidth } from './EditorLineWidth'
|
||||
import { PrefKey, PrefValue } from './PrefKey'
|
||||
import { NewNoteTitleFormat } from './NewNoteTitleFormat'
|
||||
|
||||
export const PrefDefaults = {
|
||||
[PrefKey.TagsPanelWidth]: 220,
|
||||
[PrefKey.NotesPanelWidth]: 350,
|
||||
[PrefKey.EditorWidth]: null,
|
||||
[PrefKey.EditorLeft]: null,
|
||||
[PrefKey.EditorMonospaceEnabled]: false,
|
||||
[PrefKey.EditorSpellcheck]: true,
|
||||
[PrefKey.EditorResizersEnabled]: false,
|
||||
[PrefKey.EditorLineHeight]: EditorLineHeight.Normal,
|
||||
[PrefKey.EditorLineWidth]: EditorLineWidth.FullWidth,
|
||||
[PrefKey.EditorFontSize]: EditorFontSize.Normal,
|
||||
[PrefKey.SortNotesBy]: CollectionSort.CreatedAt,
|
||||
[PrefKey.SortNotesReverse]: false,
|
||||
[PrefKey.NotesShowArchived]: false,
|
||||
[PrefKey.NotesShowTrashed]: false,
|
||||
[PrefKey.NotesHidePinned]: false,
|
||||
[PrefKey.NotesHideProtected]: false,
|
||||
[PrefKey.NotesHideNotePreview]: false,
|
||||
[PrefKey.NotesHideDate]: false,
|
||||
[PrefKey.NotesHideTags]: false,
|
||||
[PrefKey.NotesHideEditorIcon]: false,
|
||||
[PrefKey.UseSystemColorScheme]: false,
|
||||
[PrefKey.AutoLightThemeIdentifier]: 'Default',
|
||||
[PrefKey.AutoDarkThemeIdentifier]: FeatureIdentifier.DarkTheme,
|
||||
[PrefKey.NoteAddToParentFolders]: true,
|
||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat.CurrentDateAndTime,
|
||||
[PrefKey.CustomNoteTitleFormat]: 'YYYY-MM-DD [at] hh:mm A',
|
||||
[PrefKey.UpdateSavingStatusIndicator]: true,
|
||||
[PrefKey.PaneGesturesEnabled]: true,
|
||||
[PrefKey.MomentsDefaultTagUuid]: undefined,
|
||||
[PrefKey.ClipperDefaultTagUuid]: undefined,
|
||||
[PrefKey.DefaultEditorIdentifier]: FeatureIdentifier.PlainEditor,
|
||||
[PrefKey.SuperNoteExportFormat]: 'json',
|
||||
[PrefKey.SystemViewPreferences]: {},
|
||||
[PrefKey.AuthenticatorNames]: '',
|
||||
[PrefKey.ComponentPreferences]: {},
|
||||
[PrefKey.ActiveThemes]: [],
|
||||
[PrefKey.ActiveComponents]: [],
|
||||
} satisfies {
|
||||
[key in PrefKey]: PrefValue[key]
|
||||
}
|
||||
@@ -2,6 +2,11 @@ import { CollectionSortProperty } from '../../Runtime/Collection/CollectionSort'
|
||||
import { EditorIdentifier, FeatureIdentifier } from '@standardnotes/features'
|
||||
import { SystemViewId } from '../SmartView'
|
||||
import { TagPreferences } from '../Tag'
|
||||
import { NewNoteTitleFormat } from './NewNoteTitleFormat'
|
||||
import { EditorLineHeight } from './EditorLineHeight'
|
||||
import { EditorLineWidth } from './EditorLineWidth'
|
||||
import { EditorFontSize } from './EditorFontSize'
|
||||
import { AllComponentPreferences } from './ComponentPreferences'
|
||||
|
||||
export enum PrefKey {
|
||||
TagsPanelWidth = 'tagsPanelWidth',
|
||||
@@ -38,37 +43,9 @@ export enum PrefKey {
|
||||
SuperNoteExportFormat = 'superNoteExportFormat',
|
||||
AuthenticatorNames = 'authenticatorNames',
|
||||
PaneGesturesEnabled = 'paneGesturesEnabled',
|
||||
}
|
||||
|
||||
export enum NewNoteTitleFormat {
|
||||
CurrentDateAndTime = 'CurrentDateAndTime',
|
||||
CurrentNoteCount = 'CurrentNoteCount',
|
||||
CustomFormat = 'CustomFormat',
|
||||
Empty = 'Empty',
|
||||
}
|
||||
|
||||
export enum EditorLineHeight {
|
||||
None = 'None',
|
||||
Tight = 'Tight',
|
||||
Snug = 'Snug',
|
||||
Normal = 'Normal',
|
||||
Relaxed = 'Relaxed',
|
||||
Loose = 'Loose',
|
||||
}
|
||||
|
||||
export enum EditorLineWidth {
|
||||
Narrow = 'Narrow',
|
||||
Wide = 'Wide',
|
||||
Dynamic = 'Dynamic',
|
||||
FullWidth = 'FullWidth',
|
||||
}
|
||||
|
||||
export enum EditorFontSize {
|
||||
ExtraSmall = 'ExtraSmall',
|
||||
Small = 'Small',
|
||||
Normal = 'Normal',
|
||||
Medium = 'Medium',
|
||||
Large = 'Large',
|
||||
ComponentPreferences = 'componentPreferences',
|
||||
ActiveThemes = 'activeThemes',
|
||||
ActiveComponents = 'activeComponents',
|
||||
}
|
||||
|
||||
export type PrefValue = {
|
||||
@@ -106,4 +83,7 @@ export type PrefValue = {
|
||||
[PrefKey.SuperNoteExportFormat]: 'json' | 'md' | 'html'
|
||||
[PrefKey.AuthenticatorNames]: string
|
||||
[PrefKey.PaneGesturesEnabled]: boolean
|
||||
[PrefKey.ComponentPreferences]: AllComponentPreferences
|
||||
[PrefKey.ActiveThemes]: string[]
|
||||
[PrefKey.ActiveComponents]: string[]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export * from './UserPrefs'
|
||||
export * from './UserPrefsMutator'
|
||||
export * from './PrefKey'
|
||||
export * from './EditorLineHeight'
|
||||
export * from './EditorFontSize'
|
||||
export * from './EditorLineWidth'
|
||||
export * from './NewNoteTitleFormat'
|
||||
export * from './ComponentPreferences'
|
||||
export * from './PrefDefaults'
|
||||
|
||||
Reference in New Issue
Block a user