refactor: native feature management (#2350)
This commit is contained in:
@@ -1,60 +1,46 @@
|
||||
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
||||
import { ComponentArea, NoteType } from '@standardnotes/features'
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import { PlainEditorMetadata, SuperEditorMetadata } from '@/Constants/Constants'
|
||||
import { FeatureIdentifier } from '@standardnotes/snjs'
|
||||
import { ComponentArea, FindNativeFeature, GetIframeAndNativeEditors } from '@standardnotes/features'
|
||||
import { getIconAndTintForNoteType } from './Items/Icons/getIconAndTintForNoteType'
|
||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||
|
||||
export type EditorOption = DropdownItem & {
|
||||
value: FeatureIdentifier
|
||||
isLabs?: boolean
|
||||
}
|
||||
|
||||
export function noteTypeForEditorOptionValue(value: EditorOption['value'], application: WebApplication): NoteType {
|
||||
if (value === FeatureIdentifier.PlainEditor) {
|
||||
return NoteType.Plain
|
||||
} else if (value === FeatureIdentifier.SuperEditor) {
|
||||
return NoteType.Super
|
||||
}
|
||||
export function getDropdownItemsForAllEditors(application: WebApplicationInterface): EditorOption[] {
|
||||
const options: EditorOption[] = []
|
||||
|
||||
const matchingEditor = application.componentManager
|
||||
.componentsForArea(ComponentArea.Editor)
|
||||
.find((editor) => editor.identifier === value)
|
||||
options.push(
|
||||
...GetIframeAndNativeEditors().map((editor) => {
|
||||
const [iconType, tint] = getIconAndTintForNoteType(editor.note_type)
|
||||
|
||||
return matchingEditor ? matchingEditor.noteType : NoteType.Unknown
|
||||
}
|
||||
return {
|
||||
label: editor.name,
|
||||
value: editor.identifier,
|
||||
...(iconType ? { icon: iconType } : null),
|
||||
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
export function getDropdownItemsForAllEditors(application: WebApplication): EditorOption[] {
|
||||
const plaintextOption: EditorOption = {
|
||||
icon: PlainEditorMetadata.icon,
|
||||
iconClassName: PlainEditorMetadata.iconClassName,
|
||||
label: PlainEditorMetadata.name,
|
||||
value: FeatureIdentifier.PlainEditor,
|
||||
}
|
||||
options.push(
|
||||
...application.componentManager
|
||||
.thirdPartyComponentsForArea(ComponentArea.Editor)
|
||||
.filter((component) => FindNativeFeature(component.identifier) === undefined)
|
||||
.map((editor): EditorOption => {
|
||||
const identifier = editor.package_info.identifier
|
||||
const [iconType, tint] = getIconAndTintForNoteType(editor.noteType)
|
||||
|
||||
const options = application.componentManager.componentsForArea(ComponentArea.Editor).map((editor): EditorOption => {
|
||||
const identifier = editor.package_info.identifier
|
||||
const [iconType, tint] = getIconAndTintForNoteType(editor.package_info.note_type)
|
||||
|
||||
return {
|
||||
label: editor.displayName,
|
||||
value: identifier,
|
||||
...(iconType ? { icon: iconType } : null),
|
||||
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
||||
}
|
||||
})
|
||||
|
||||
options.push(plaintextOption)
|
||||
|
||||
if (application.features.getFeatureStatus(FeatureIdentifier.SuperEditor) === FeatureStatus.Entitled) {
|
||||
options.push({
|
||||
icon: SuperEditorMetadata.icon,
|
||||
iconClassName: SuperEditorMetadata.iconClassName,
|
||||
label: SuperEditorMetadata.name,
|
||||
value: FeatureIdentifier.SuperEditor,
|
||||
isLabs: true,
|
||||
})
|
||||
}
|
||||
return {
|
||||
label: editor.displayName,
|
||||
value: identifier,
|
||||
...(iconType ? { icon: iconType } : null),
|
||||
...(tint ? { iconClassName: `text-accessory-tint-${tint}` } : null),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
options.sort((a, b) => {
|
||||
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlainEditorMetadata, SuperEditorMetadata } from '@/Constants/Constants'
|
||||
import { SuperEditorMetadata } from '@/Constants/Constants'
|
||||
import { NoteType } from '@standardnotes/features'
|
||||
import { IconType } from '@standardnotes/models'
|
||||
|
||||
@@ -6,7 +6,7 @@ export function getIconAndTintForNoteType(noteType?: NoteType, subtle?: boolean)
|
||||
switch (noteType) {
|
||||
case undefined:
|
||||
case NoteType.Plain:
|
||||
return [PlainEditorMetadata.icon, PlainEditorMetadata.iconTintNumber]
|
||||
return ['plain-text', 1]
|
||||
case NoteType.RichText:
|
||||
return ['rich-text', 1]
|
||||
case NoteType.Markdown:
|
||||
@@ -26,6 +26,6 @@ export function getIconAndTintForNoteType(noteType?: NoteType, subtle?: boolean)
|
||||
]
|
||||
case NoteType.Unknown:
|
||||
default:
|
||||
return ['editor', PlainEditorMetadata.iconTintNumber]
|
||||
return ['editor', 1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IconType, FileItem, SNNote, DecryptedItem, SNTag } from '@standardnotes/snjs'
|
||||
import { IconType, FileItem, SNNote, SNTag, DecryptedItemInterface } from '@standardnotes/snjs'
|
||||
import { getIconAndTintForNoteType } from './getIconAndTintForNoteType'
|
||||
import { getIconForFileType } from './getIconForFileType'
|
||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||
|
||||
export function getIconForItem(item: DecryptedItem, application: WebApplicationInterface): [IconType, string] {
|
||||
export function getIconForItem(item: DecryptedItemInterface, application: WebApplicationInterface): [IconType, string] {
|
||||
if (item instanceof SNNote) {
|
||||
const editorForNote = application.componentManager.editorForNote(item)
|
||||
const [icon, tint] = getIconAndTintForNoteType(editorForNote?.package_info.note_type)
|
||||
const [icon, tint] = getIconAndTintForNoteType(editorForNote.noteType)
|
||||
const className = `text-accessory-tint-${tint}`
|
||||
return [icon, className]
|
||||
} else if (item instanceof FileItem) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Environment, SNApplication } from '@standardnotes/snjs'
|
||||
import { Environment } from '@standardnotes/snjs'
|
||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||
|
||||
export async function openSubscriptionDashboard(application: SNApplication) {
|
||||
export async function openSubscriptionDashboard(application: WebApplicationInterface) {
|
||||
const token = await application.getNewSubscriptionToken()
|
||||
if (!token) {
|
||||
return
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'
|
||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||
import { NoteType, PrefKey, SNNote } from '@standardnotes/snjs'
|
||||
import { NoteType, PrefKey, SNNote, PrefDefaults } from '@standardnotes/snjs'
|
||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||
|
||||
export const getNoteFormat = (application: WebApplication, note: SNNote) => {
|
||||
const editor = application.componentManager.editorForNote(note)
|
||||
|
||||
const isSuperNote = note.noteType === NoteType.Super
|
||||
|
||||
if (isSuperNote) {
|
||||
export const getNoteFormat = (application: WebApplicationInterface, note: SNNote) => {
|
||||
if (note.noteType === NoteType.Super) {
|
||||
const superNoteExportFormatPref = application.getPreference(
|
||||
PrefKey.SuperNoteExportFormat,
|
||||
PrefDefaults[PrefKey.SuperNoteExportFormat],
|
||||
@@ -17,15 +12,16 @@ export const getNoteFormat = (application: WebApplication, note: SNNote) => {
|
||||
return superNoteExportFormatPref
|
||||
}
|
||||
|
||||
return editor?.package_info?.file_type || 'txt'
|
||||
const editor = application.componentManager.editorForNote(note)
|
||||
return editor.fileType
|
||||
}
|
||||
|
||||
export const getNoteFileName = (application: WebApplication, note: SNNote): string => {
|
||||
export const getNoteFileName = (application: WebApplicationInterface, note: SNNote): string => {
|
||||
const format = getNoteFormat(application, note)
|
||||
return `${note.title}.${format}`
|
||||
}
|
||||
|
||||
export const getNoteBlob = (application: WebApplication, note: SNNote) => {
|
||||
export const getNoteBlob = (application: WebApplicationInterface, note: SNNote) => {
|
||||
const format = getNoteFormat(application, note)
|
||||
let type: string
|
||||
switch (format) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { ThemeItem } from '@/Components/QuickSettingsMenu/ThemeItem'
|
||||
import { FeatureIdentifier } from '@standardnotes/snjs'
|
||||
import { ComponentOrNativeFeature, FeatureIdentifier, ThemeFeatureDescription } from '@standardnotes/snjs'
|
||||
|
||||
const isDarkModeTheme = (theme: ThemeItem) => theme.identifier === FeatureIdentifier.DarkTheme
|
||||
const isDarkModeTheme = (theme: ComponentOrNativeFeature<ThemeFeatureDescription>) =>
|
||||
theme.featureIdentifier === FeatureIdentifier.DarkTheme
|
||||
|
||||
export const sortThemes = (a: ThemeItem, b: ThemeItem) => {
|
||||
const aIsLayerable = a.component?.isLayerable()
|
||||
const bIsLayerable = b.component?.isLayerable()
|
||||
export const sortThemes = (
|
||||
a: ComponentOrNativeFeature<ThemeFeatureDescription>,
|
||||
b: ComponentOrNativeFeature<ThemeFeatureDescription>,
|
||||
) => {
|
||||
const aIsLayerable = a.layerable
|
||||
const bIsLayerable = b.layerable
|
||||
|
||||
if (aIsLayerable && !bIsLayerable) {
|
||||
return 1
|
||||
@@ -14,6 +17,6 @@ export const sortThemes = (a: ThemeItem, b: ThemeItem) => {
|
||||
} else if (!isDarkModeTheme(a) && isDarkModeTheme(b)) {
|
||||
return 1
|
||||
} else {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
|
||||
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,66 @@
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import {
|
||||
ContentType,
|
||||
FeatureStatus,
|
||||
SNComponent,
|
||||
ComponentArea,
|
||||
FeatureDescription,
|
||||
GetFeatures,
|
||||
FindNativeFeature,
|
||||
NoteType,
|
||||
FeatureIdentifier,
|
||||
GetIframeAndNativeEditors,
|
||||
ComponentArea,
|
||||
GetSuperNoteFeature,
|
||||
ComponentOrNativeFeature,
|
||||
IframeComponentFeatureDescription,
|
||||
} from '@standardnotes/snjs'
|
||||
import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
||||
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
||||
import { PlainEditorMetadata, SuperEditorMetadata } from '@/Constants/Constants'
|
||||
import { SuperEditorMetadata } from '@/Constants/Constants'
|
||||
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||
|
||||
type NoteTypeToEditorRowsMap = Record<NoteType, EditorMenuItem[]>
|
||||
|
||||
const getNoteTypeForFeatureDescription = (featureDescription: FeatureDescription): NoteType => {
|
||||
if (featureDescription.note_type) {
|
||||
return featureDescription.note_type
|
||||
} else if (featureDescription.file_type) {
|
||||
switch (featureDescription.file_type) {
|
||||
case 'html':
|
||||
return NoteType.RichText
|
||||
case 'md':
|
||||
return NoteType.Markdown
|
||||
const insertNativeEditorsInMap = (map: NoteTypeToEditorRowsMap, application: WebApplicationInterface): void => {
|
||||
for (const editorFeature of GetIframeAndNativeEditors()) {
|
||||
const isExperimental = application.features.isExperimentalFeature(editorFeature.identifier)
|
||||
if (isExperimental) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return NoteType.Unknown
|
||||
}
|
||||
|
||||
const insertNonInstalledNativeComponentsInMap = (
|
||||
map: NoteTypeToEditorRowsMap,
|
||||
components: SNComponent[],
|
||||
application: WebApplication,
|
||||
): void => {
|
||||
GetFeatures()
|
||||
.filter((feature) => feature.content_type === ContentType.TYPES.Component && feature.area === ComponentArea.Editor)
|
||||
.forEach((editorFeature) => {
|
||||
const notInstalled = !components.find((editor) => editor.identifier === editorFeature.identifier)
|
||||
const isExperimental = application.features.isExperimentalFeature(editorFeature.identifier)
|
||||
const isDeprecated = editorFeature.deprecated
|
||||
const isShowable = notInstalled && !isExperimental && !isDeprecated
|
||||
const isDeprecated = editorFeature.deprecated
|
||||
if (isDeprecated) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (isShowable) {
|
||||
const noteType = getNoteTypeForFeatureDescription(editorFeature)
|
||||
map[noteType].push({
|
||||
name: editorFeature.name as string,
|
||||
isEntitled: false,
|
||||
noteType,
|
||||
})
|
||||
}
|
||||
const noteType = editorFeature.note_type
|
||||
map[noteType].push({
|
||||
isEntitled: application.features.getFeatureStatus(editorFeature.identifier) === FeatureStatus.Entitled,
|
||||
uiFeature: new ComponentOrNativeFeature(editorFeature),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const insertInstalledComponentsInMap = (
|
||||
map: NoteTypeToEditorRowsMap,
|
||||
components: SNComponent[],
|
||||
application: WebApplication,
|
||||
) => {
|
||||
components.forEach((editor) => {
|
||||
const noteType = getNoteTypeForFeatureDescription(editor.package_info)
|
||||
const insertInstalledComponentsInMap = (map: NoteTypeToEditorRowsMap, application: WebApplicationInterface) => {
|
||||
const thirdPartyOrInstalledEditors = application.componentManager
|
||||
.thirdPartyComponentsForArea(ComponentArea.Editor)
|
||||
.sort((a, b) => {
|
||||
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1
|
||||
})
|
||||
|
||||
for (const editor of thirdPartyOrInstalledEditors) {
|
||||
const nativeFeature = FindNativeFeature(editor.identifier)
|
||||
if (nativeFeature && !nativeFeature.deprecated) {
|
||||
continue
|
||||
}
|
||||
|
||||
const noteType = editor.noteType
|
||||
|
||||
const editorItem: EditorMenuItem = {
|
||||
name: editor.displayName,
|
||||
component: editor,
|
||||
uiFeature: new ComponentOrNativeFeature<IframeComponentFeatureDescription>(editor),
|
||||
isEntitled: application.features.getFeatureStatus(editor.identifier) === FeatureStatus.Entitled,
|
||||
noteType,
|
||||
}
|
||||
|
||||
map[noteType].push(editorItem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const createGroupsFromMap = (map: NoteTypeToEditorRowsMap, _application: WebApplication): EditorMenuGroup[] => {
|
||||
const createGroupsFromMap = (map: NoteTypeToEditorRowsMap): EditorMenuGroup[] => {
|
||||
const superNote = GetSuperNoteFeature()
|
||||
const groups: EditorMenuGroup[] = [
|
||||
{
|
||||
icon: 'plain-text',
|
||||
@@ -84,7 +71,7 @@ const createGroupsFromMap = (map: NoteTypeToEditorRowsMap, _application: WebAppl
|
||||
{
|
||||
icon: SuperEditorMetadata.icon,
|
||||
iconClassName: SuperEditorMetadata.iconClassName,
|
||||
title: SuperEditorMetadata.name,
|
||||
title: superNote.name,
|
||||
items: map[NoteType.Super],
|
||||
featured: true,
|
||||
},
|
||||
@@ -135,23 +122,10 @@ const createGroupsFromMap = (map: NoteTypeToEditorRowsMap, _application: WebAppl
|
||||
return groups
|
||||
}
|
||||
|
||||
const createBaselineMap = (application: WebApplication): NoteTypeToEditorRowsMap => {
|
||||
const createBaselineMap = (): NoteTypeToEditorRowsMap => {
|
||||
const map: NoteTypeToEditorRowsMap = {
|
||||
[NoteType.Plain]: [
|
||||
{
|
||||
name: PlainEditorMetadata.name,
|
||||
isEntitled: true,
|
||||
noteType: NoteType.Plain,
|
||||
},
|
||||
],
|
||||
[NoteType.Super]: [
|
||||
{
|
||||
name: SuperEditorMetadata.name,
|
||||
isEntitled: application.features.getFeatureStatus(FeatureIdentifier.SuperEditor) === FeatureStatus.Entitled,
|
||||
noteType: NoteType.Super,
|
||||
description: FindNativeFeature(FeatureIdentifier.SuperEditor)?.description,
|
||||
},
|
||||
],
|
||||
[NoteType.Plain]: [],
|
||||
[NoteType.Super]: [],
|
||||
[NoteType.RichText]: [],
|
||||
[NoteType.Markdown]: [],
|
||||
[NoteType.Task]: [],
|
||||
@@ -164,12 +138,12 @@ const createBaselineMap = (application: WebApplication): NoteTypeToEditorRowsMap
|
||||
return map
|
||||
}
|
||||
|
||||
export const createEditorMenuGroups = (application: WebApplication, components: SNComponent[]) => {
|
||||
const map = createBaselineMap(application)
|
||||
export const createEditorMenuGroups = (application: WebApplicationInterface): EditorMenuGroup[] => {
|
||||
const map = createBaselineMap()
|
||||
|
||||
insertNonInstalledNativeComponentsInMap(map, components, application)
|
||||
insertNativeEditorsInMap(map, application)
|
||||
|
||||
insertInstalledComponentsInMap(map, components, application)
|
||||
insertInstalledComponentsInMap(map, application)
|
||||
|
||||
return createGroupsFromMap(map, application)
|
||||
return createGroupsFromMap(map)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user