+
{children != undefined && Array.isArray(children)
? children.map((child, idx, arr) => (
- <>
- {child}
- {idx < arr.length - 1 ?
: undefined}
- >
- ))
+ <>
+ {child}
+ {idx < arr.length - 1 ?
: undefined}
+ >
+ ))
: children}
>
diff --git a/app/assets/javascripts/components/utils.ts b/app/assets/javascripts/components/utils.ts
index fd78955f8..13c1ce001 100644
--- a/app/assets/javascripts/components/utils.ts
+++ b/app/assets/javascripts/components/utils.ts
@@ -1,4 +1,5 @@
import { FunctionComponent, h, render } from 'preact';
+import { unmountComponentAtNode } from 'preact/compat';
import { StateUpdater, useCallback, useState } from 'preact/hooks';
import { useEffect } from 'react';
@@ -8,7 +9,7 @@ import { useEffect } from 'react';
* monitored.
*/
export function useCloseOnBlur(
- container: { current: HTMLDivElement },
+ container: { current?: HTMLDivElement },
setOpen: (open: boolean) => void
): [
(event: { relatedTarget: EventTarget | null }) => void,
@@ -70,6 +71,9 @@ export function toDirective
(
$onChanges() {
render(h(component, $scope), $element[0]);
},
+ $onDestroy() {
+ unmountComponentAtNode($element[0]);
+ },
};
},
],
diff --git a/app/assets/javascripts/directives/views/componentView.ts b/app/assets/javascripts/directives/views/componentView.ts
deleted file mode 100644
index 67392b335..000000000
--- a/app/assets/javascripts/directives/views/componentView.ts
+++ /dev/null
@@ -1,294 +0,0 @@
-import { RootScopeMessages } from './../../messages';
-import { WebApplication } from '@/ui_models/application';
-import { SNComponent, ComponentAction, LiveItem } from '@standardnotes/snjs';
-import { WebDirective } from './../../types';
-import template from '%/directives/component-view.pug';
-import { isDesktopApplication } from '../../utils';
-/**
- * The maximum amount of time we'll wait for a component
- * to load before displaying error
- */
-const MaxLoadThreshold = 4000;
-const VisibilityChangeKey = 'visibilitychange';
-
-interface ComponentViewScope {
- componentUuid: string
- onLoad?: (component: SNComponent) => void
- application: WebApplication
-}
-
-class ComponentViewCtrl implements ComponentViewScope {
-
- /** @scope */
- onLoad?: (component: SNComponent) => void
- componentUuid!: string
- templateComponent!: SNComponent
- application!: WebApplication
- liveComponent!: LiveItem
-
- private $rootScope: ng.IRootScopeService
- private $timeout: ng.ITimeoutService
- private componentValid = true
- private cleanUpOn: () => void
- private unregisterComponentHandler!: () => void
- private unregisterDesktopObserver!: () => void
- private issueLoading = false
- private isDeprecated = false
- private deprecationMessage: string | undefined = undefined
- private deprecationMessageDismissed = false
- public reloading = false
- private expired = false
- private loading = false
- private didAttemptReload = false
- public error: 'offline-restricted' | 'url-missing' | undefined
- private loadTimeout: any
-
- /* @ngInject */
- constructor(
- $scope: ng.IScope,
- $rootScope: ng.IRootScopeService,
- $timeout: ng.ITimeoutService,
- ) {
- this.$rootScope = $rootScope;
- this.$timeout = $timeout;
- this.cleanUpOn = $scope.$on('ext-reload-complete', () => {
- this.reloadStatus(false);
- });
- /** To allow for registering events */
- this.onVisibilityChange = this.onVisibilityChange.bind(this);
- }
-
- $onDestroy() {
- if(this.application.componentManager) {
- /** Component manager Can be destroyed already via locking */
- this.application.componentManager.onComponentIframeDestroyed(this.component.uuid);
- if(this.templateComponent) {
- this.application.componentManager.removeTemporaryTemplateComponent(this.templateComponent);
- }
- }
- if(this.liveComponent) {
- this.liveComponent.deinit();
- }
- this.cleanUpOn();
- (this.cleanUpOn as any) = undefined;
- this.unregisterComponentHandler();
- (this.unregisterComponentHandler as any) = undefined;
- this.unregisterDesktopObserver();
- (this.unregisterDesktopObserver as any) = undefined;
- (this.templateComponent as any) = undefined;
- (this.liveComponent as any) = undefined;
- (this.application as any) = undefined;
- (this.onVisibilityChange as any) = undefined;
- this.onLoad = undefined;
- document.removeEventListener(
- VisibilityChangeKey,
- this.onVisibilityChange
- );
- }
-
- $onInit() {
- if(this.componentUuid) {
- this.liveComponent = new LiveItem(this.componentUuid, this.application);
- } else {
- this.application.componentManager.addTemporaryTemplateComponent(this.templateComponent);
- }
- this.registerComponentHandlers();
- this.registerPackageUpdateObserver();
- }
-
- get component() {
- return this.templateComponent || this.liveComponent?.item;
- }
-
- /** @template */
- public onIframeInit() {
- /** Perform in timeout required so that dynamic iframe id is set (based on ctrl values) */
- this.$timeout(() => {
- this.loadComponent();
- });
- }
-
- private loadComponent() {
- if (!this.component) {
- throw Error('Component view is missing component');
- }
- if (!this.component.active && !this.component.isEditor()) {
- /** Editors don't need to be active to be displayed */
- throw Error('Component view component must be active');
- }
- const iframe = this.application.componentManager!.iframeForComponent(
- this.component.uuid
- );
- if (!iframe) {
- return;
- }
- this.loading = true;
- if (this.loadTimeout) {
- this.$timeout.cancel(this.loadTimeout);
- }
- this.loadTimeout = this.$timeout(() => {
- this.handleIframeLoadTimeout();
- }, MaxLoadThreshold);
- iframe.onload = () => {
- this.reloadStatus();
- this.handleIframeLoad(iframe);
- };
- }
-
- private registerPackageUpdateObserver() {
- this.unregisterDesktopObserver = this.application.getDesktopService()
- .registerUpdateObserver((component: SNComponent) => {
- if (component.uuid === this.component.uuid && component.active) {
- this.reloadIframe();
- }
- });
- }
-
- private registerComponentHandlers() {
- this.unregisterComponentHandler = this.application.componentManager!.registerHandler({
- identifier: 'component-view-' + Math.random(),
- areas: [this.component.area],
- actionHandler: (component, action, data) => {
- switch (action) {
- case (ComponentAction.SetSize):
- this.application.componentManager!.handleSetSizeEvent(component, data);
- break;
- case (ComponentAction.KeyDown):
- this.application.io.handleComponentKeyDown(data.keyboardModifier);
- break;
- case (ComponentAction.KeyUp):
- this.application.io.handleComponentKeyUp(data.keyboardModifier);
- break;
- case (ComponentAction.Click):
- this.application.getAppState().notes.setContextMenuOpen(false);
- break;
- default:
- return;
- }
- }
- });
- }
-
- private reloadIframe() {
- this.$timeout(() => {
- this.reloading = true;
- this.$timeout(() => {
- this.reloading = false;
- });
- });
- }
-
- private dismissDeprecationMessage() {
- this.$timeout(() => {
- this.deprecationMessageDismissed = true;
- });
- }
-
- private onVisibilityChange() {
- if (document.visibilityState === 'hidden') {
- return;
- }
- if (this.issueLoading) {
- this.reloadIframe();
- }
- }
-
- public reloadStatus(doManualReload = true) {
- const component = this.component;
- const offlineRestricted = component.offlineOnly && !isDesktopApplication();
- const hasUrlError = function () {
- if (isDesktopApplication()) {
- return !component.local_url && !component.hasValidHostedUrl();
- } else {
- return !component.hasValidHostedUrl();
- }
- }();
- this.expired = component.valid_until && component.valid_until <= new Date();
- const readonlyState = this.application.componentManager!
- .getReadonlyStateForComponent(component);
- if (!readonlyState.lockReadonly) {
- this.application.componentManager!
- .setReadonlyStateForComponent(component, this.expired);
- }
- this.componentValid = !offlineRestricted && !hasUrlError;
- if (!this.componentValid) {
- this.loading = false;
- }
- if (offlineRestricted) {
- this.error = 'offline-restricted';
- } else if (hasUrlError) {
- this.error = 'url-missing';
- } else {
- this.error = undefined;
- }
- if (this.expired && doManualReload) {
- this.$rootScope.$broadcast(RootScopeMessages.ReloadExtendedData);
- }
- this.isDeprecated = component.isDeprecated;
- this.deprecationMessage = component.package_info.deprecation_message;
- }
-
- private async handleIframeLoadTimeout() {
- if (this.loading) {
- this.loading = false;
- this.issueLoading = true;
- if (!this.didAttemptReload) {
- this.didAttemptReload = true;
- this.reloadIframe();
- } else {
- document.addEventListener(
- VisibilityChangeKey,
- this.onVisibilityChange
- );
- }
- }
- }
-
- private async handleIframeLoad(iframe: HTMLIFrameElement) {
- let desktopError = false;
- if (isDesktopApplication()) {
- try {
- /** Accessing iframe.contentWindow.origin only allowed in desktop app. */
- if (!iframe.contentWindow!.origin || iframe.contentWindow!.origin === 'null') {
- desktopError = true;
- }
- // eslint-disable-next-line no-empty
- } catch (e) { }
- }
- this.$timeout.cancel(this.loadTimeout);
- await this.application.componentManager!.registerComponentWindow(
- this.component,
- iframe.contentWindow!
- );
- const avoidFlickerTimeout = 7;
- this.$timeout(() => {
- this.loading = false;
- // eslint-disable-next-line no-unneeded-ternary
- this.issueLoading = desktopError ? true : false;
- this.onLoad && this.onLoad(this.component!);
- }, avoidFlickerTimeout);
- }
-
- /** @template */
- public getUrl() {
- const url = this.application.componentManager!.urlForComponent(this.component);
- return url;
- }
-}
-
-export class ComponentView extends WebDirective {
- constructor() {
- super();
- this.restrict = 'E';
- this.template = template;
- this.scope = {
- componentUuid: '=',
- templateComponent: '=?',
- onLoad: '=?',
- application: '='
- };
- this.controller = ComponentViewCtrl;
- this.controllerAs = 'ctrl';
- this.bindToController = true;
- }
-}
diff --git a/app/assets/javascripts/directives/views/index.ts b/app/assets/javascripts/directives/views/index.ts
index 273a3e944..ee69ce822 100644
--- a/app/assets/javascripts/directives/views/index.ts
+++ b/app/assets/javascripts/directives/views/index.ts
@@ -1,6 +1,5 @@
export { ActionsMenu } from './actionsMenu';
export { ComponentModal } from './componentModal';
-export { ComponentView } from './componentView';
export { EditorMenu } from './editorMenu';
export { InputModal } from './inputModal';
export { MenuRow } from './menuRow';
diff --git a/app/assets/javascripts/enums.ts b/app/assets/javascripts/enums.ts
index 199fab2ff..e19ae9350 100644
--- a/app/assets/javascripts/enums.ts
+++ b/app/assets/javascripts/enums.ts
@@ -20,5 +20,5 @@ export enum HtmlInputTypes {
Text = 'text',
Time = 'time',
Url = 'url',
- Week = 'week'
+ Week = 'week',
}
diff --git a/app/assets/javascripts/messages.ts b/app/assets/javascripts/messages.ts
index 2dcecff72..8e7ede0ac 100644
--- a/app/assets/javascripts/messages.ts
+++ b/app/assets/javascripts/messages.ts
@@ -1,4 +1,3 @@
export enum RootScopeMessages {
- ReloadExtendedData = 'reload-ext-data',
NewUpdateAvailable = 'new-update-available'
}
diff --git a/app/assets/javascripts/preferences/PreferencesMenu.ts b/app/assets/javascripts/preferences/PreferencesMenu.ts
index 0e2b02e54..b55a78e63 100644
--- a/app/assets/javascripts/preferences/PreferencesMenu.ts
+++ b/app/assets/javascripts/preferences/PreferencesMenu.ts
@@ -1,12 +1,16 @@
import { IconType } from '@/components/Icon';
-import { makeAutoObservable, observable } from 'mobx';
+import { action, makeAutoObservable, observable } from 'mobx';
+import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
+import { ContentType, SNComponent } from '@node_modules/@standardnotes/snjs';
+import { WebApplication } from '@/ui_models/application';
+import { FeatureIdentifier } from '@node_modules/@standardnotes/features/dist/Domain/Feature/FeatureIdentifier';
+import { ComponentArea } from '@standardnotes/snjs';
const PREFERENCE_IDS = [
'general',
'account',
'appearance',
'security',
- 'extensions',
'listed',
'shortcuts',
'accessibility',
@@ -16,20 +20,23 @@ const PREFERENCE_IDS = [
export type PreferenceId = typeof PREFERENCE_IDS[number];
interface PreferencesMenuItem {
- readonly id: PreferenceId;
+ readonly id: PreferenceId | FeatureIdentifier;
readonly icon: IconType;
readonly label: string;
}
+interface SelectableMenuItem extends PreferencesMenuItem {
+ selected: boolean;
+}
+
/**
* Items are in order of appearance
*/
const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
- { id: 'general', label: 'General', icon: 'settings' },
{ id: 'account', label: 'Account', icon: 'user' },
+ { id: 'general', label: 'General', icon: 'settings' },
{ id: 'appearance', label: 'Appearance', icon: 'themes' },
{ id: 'security', label: 'Security', icon: 'security' },
- { id: 'extensions', label: 'Extensions', icon: 'tune' },
{ id: 'listed', label: 'Listed', icon: 'listed' },
{ id: 'shortcuts', label: 'Shortcuts', icon: 'keyboard' },
{ id: 'accessibility', label: 'Accessibility', icon: 'accessibility' },
@@ -38,46 +45,118 @@ const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
];
const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
- { id: 'general', label: 'General', icon: 'settings' },
{ id: 'account', label: 'Account', icon: 'user' },
+ { id: 'general', label: 'General', icon: 'settings' },
{ id: 'security', label: 'Security', icon: 'security' },
{ id: 'listed', label: 'Listed', icon: 'listed' },
{ id: 'help-feedback', label: 'Help & feedback', icon: 'help' },
];
export class PreferencesMenu {
- private _selectedPane: PreferenceId = 'general';
+ private _selectedPane: PreferenceId | FeatureIdentifier = 'account';
+ private _extensionPanes: SNComponent[] = [];
private _menu: PreferencesMenuItem[];
+ private _extensionLatestVersions: ExtensionsLatestVersions =
+ new ExtensionsLatestVersions(new Map());
constructor(
- private readonly _enableUnfinishedFeatures: boolean,
+ private application: WebApplication,
+ private readonly _enableUnfinishedFeatures: boolean
) {
- this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS;
- makeAutoObservable(
- this,
- {
- _twoFactorAuth: observable,
- _selectedPane: observable,
+ this._menu = this._enableUnfinishedFeatures
+ ? PREFERENCES_MENU_ITEMS
+ : READY_PREFERENCES_MENU_ITEMS;
+
+ this.loadExtensionsPanes();
+ this.loadLatestVersions();
+
+ makeAutoObservable<
+ PreferencesMenu,
+ | '_selectedPane'
+ | '_twoFactorAuth'
+ | '_extensionPanes'
+ | '_extensionLatestVersions'
+ | 'loadLatestVersions'
+ >(this, {
+ _twoFactorAuth: observable,
+ _selectedPane: observable,
+ _extensionPanes: observable.ref,
+ _extensionLatestVersions: observable.ref,
+ loadLatestVersions: action,
+ });
+ }
+
+ private loadLatestVersions(): void {
+ ExtensionsLatestVersions.load(this.application).then((versions) => {
+ this._extensionLatestVersions = versions;
+ });
+ }
+
+ get extensionsLatestVersions(): ExtensionsLatestVersions {
+ return this._extensionLatestVersions;
+ }
+
+ loadExtensionsPanes(): void {
+ const excludedComponents = [
+ FeatureIdentifier.TwoFactorAuthManager,
+ 'org.standardnotes.batch-manager',
+ 'org.standardnotes.extensions-manager',
+ ];
+ this._extensionPanes = (
+ this.application.getItems([
+ ContentType.ActionsExtension,
+ ContentType.Component,
+ ContentType.Theme,
+ ]) as SNComponent[]
+ ).filter(
+ (extension) =>
+ extension.area === ComponentArea.Modal &&
+ !excludedComponents.includes(extension.package_info.identifier)
+ );
+ }
+
+ get menuItems(): SelectableMenuItem[] {
+ const menuItems = this._menu.map((preference) => ({
+ ...preference,
+ selected: preference.id === this._selectedPane,
+ }));
+ const extensionsMenuItems: SelectableMenuItem[] = this._extensionPanes.map(
+ (extension) => {
+ return {
+ icon: 'window',
+ id: extension.package_info.identifier,
+ label: extension.name,
+ selected: extension.package_info.identifier === this._selectedPane,
+ };
}
);
+
+ return menuItems.concat(extensionsMenuItems);
}
- get menuItems(): (PreferencesMenuItem & {
- selected: boolean;
- })[] {
- return this._menu.map((p) => ({
- ...p,
- selected: p.id === this._selectedPane,
- }));
+ get selectedMenuItem(): PreferencesMenuItem | undefined {
+ return this._menu.find((item) => item.id === this._selectedPane);
}
- get selectedPaneId(): PreferenceId {
- return (
- this._menu.find((item) => item.id === this._selectedPane)?.id ?? 'general'
+ get selectedExtension(): SNComponent | undefined {
+ return this._extensionPanes.find(
+ (extension) => extension.package_info.identifier === this._selectedPane
);
}
- selectPane(key: PreferenceId): void {
+ get selectedPaneId(): PreferenceId | FeatureIdentifier {
+ if (this.selectedMenuItem != undefined) {
+ return this.selectedMenuItem.id;
+ }
+
+ if (this.selectedExtension != undefined) {
+ return this.selectedExtension.package_info.identifier;
+ }
+
+ return 'account';
+ }
+
+ selectPane(key: PreferenceId | FeatureIdentifier): void {
this._selectedPane = key;
}
}
diff --git a/app/assets/javascripts/preferences/PreferencesView.tsx b/app/assets/javascripts/preferences/PreferencesView.tsx
index 561ff44d7..cd58f05a3 100644
--- a/app/assets/javascripts/preferences/PreferencesView.tsx
+++ b/app/assets/javascripts/preferences/PreferencesView.tsx
@@ -9,13 +9,14 @@ import {
Security,
} from './panes';
import { observer } from 'mobx-react-lite';
+
import { PreferencesMenu } from './PreferencesMenu';
import { PreferencesMenuView } from './PreferencesMenuView';
import { WebApplication } from '@/ui_models/application';
import { MfaProps } from './panes/two-factor-auth/MfaProps';
import { AppState } from '@/ui_models/app_state';
import { useEffect, useMemo } from 'preact/hooks';
-import { Extensions } from './panes/Extensions';
+import { ExtensionPane } from './panes/ExtensionPane';
interface PreferencesProps extends MfaProps {
application: WebApplication;
@@ -25,44 +26,72 @@ interface PreferencesProps extends MfaProps {
const PaneSelector: FunctionComponent<
PreferencesProps & { menu: PreferencesMenu }
-> = observer((props) => {
- switch (props.menu.selectedPaneId) {
- case 'general':
- return (
-
- );
- case 'account':
- return (
-
- );
- case 'appearance':
- return null;
- case 'security':
- return (
-
- );
- case 'extensions':
- return ;
- case 'listed':
- return ;
- case 'shortcuts':
- return null;
- case 'accessibility':
- return null;
- case 'get-free-month':
- return null;
- case 'help-feedback':
- return ;
- }
-});
+> = observer(
+ ({
+ menu,
+ appState,
+ application,
+ mfaProvider,
+ userProvider
+ }) => {
+ switch (menu.selectedPaneId) {
+ case 'general':
+ return (
+
+ );
+ case 'account':
+ return (
+
+ );
+ case 'appearance':
+ return null;
+ case 'security':
+ return (
+
+ );
+ case 'listed':
+ return ;
+ case 'shortcuts':
+ return null;
+ case 'accessibility':
+ return null;
+ case 'get-free-month':
+ return null;
+ case 'help-feedback':
+ return ;
+ default:
+ if (menu.selectedExtension != undefined) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ }
+ });
const PreferencesCanvas: FunctionComponent<
PreferencesProps & { menu: PreferencesMenu }
@@ -75,9 +104,9 @@ const PreferencesCanvas: FunctionComponent<
export const PreferencesView: FunctionComponent = observer(
(props) => {
- const menu = useMemo(() => new PreferencesMenu(props.appState.enableUnfinishedFeatures), [
- props.appState.enableUnfinishedFeatures
- ]);
+ const menu = useMemo(
+ () => new PreferencesMenu(props.application, props.appState.enableUnfinishedFeatures),
+ [props.appState.enableUnfinishedFeatures, props.application]);
useEffect(() => {
menu.selectPane(props.appState.preferences.currentPane);
diff --git a/app/assets/javascripts/preferences/components/Content.tsx b/app/assets/javascripts/preferences/components/Content.tsx
index a560ee799..31dcc247a 100644
--- a/app/assets/javascripts/preferences/components/Content.tsx
+++ b/app/assets/javascripts/preferences/components/Content.tsx
@@ -4,18 +4,28 @@ export const Title: FunctionComponent = ({ children }) => (
{children}
);
-export const Subtitle: FunctionComponent<{ className?: string }> = ({ children, className = "" }) => (
+export const Subtitle: FunctionComponent<{ className?: string }> = ({
+ children,
+ className = '',
+}) => (
{children}
);
+export const SubtitleLight: FunctionComponent<{ className?: string }> = ({
+ children,
+ className = '',
+}) => (
+ {children}
+);
+
export const Text: FunctionComponent<{ className?: string }> = ({
children,
className = '',
}) => {children}
;
const buttonClasses = `block bg-default color-text rounded border-solid \
-border-1 border-gray-300 px-4 py-1.75 font-bold text-sm fit-content \
-focus:bg-contrast hover:bg-contrast`;
+border-1 px-4 py-1.75 font-bold text-sm fit-content \
+focus:bg-contrast hover:bg-contrast border-main`;
export const LinkButton: FunctionComponent<{
label: string;
diff --git a/app/assets/javascripts/preferences/components/PreferencesGroup.tsx b/app/assets/javascripts/preferences/components/PreferencesGroup.tsx
index 3d44f7c05..a40d0e281 100644
--- a/app/assets/javascripts/preferences/components/PreferencesGroup.tsx
+++ b/app/assets/javascripts/preferences/components/PreferencesGroup.tsx
@@ -7,18 +7,18 @@ const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({
}) => (index < length - 1 ? : null);
export const PreferencesGroup: FunctionComponent = ({ children }) => (
-
+
{Array.isArray(children)
? children
- .filter(
- (child) => child != undefined && child !== '' && child !== false
- )
- .map((child, i, arr) => (
- <>
- {child}
-
- >
- ))
+ .filter(
+ (child) => child != undefined && child !== '' && child !== false
+ )
+ .map((child, i, arr) => (
+ <>
+ {child}
+
+ >
+ ))
: children}
);
diff --git a/app/assets/javascripts/preferences/components/PreferencesSegment.tsx b/app/assets/javascripts/preferences/components/PreferencesSegment.tsx
index dad798f5c..4e12a79ce 100644
--- a/app/assets/javascripts/preferences/components/PreferencesSegment.tsx
+++ b/app/assets/javascripts/preferences/components/PreferencesSegment.tsx
@@ -1,5 +1,11 @@
import { FunctionComponent } from 'preact';
-export const PreferencesSegment: FunctionComponent = ({ children }) => (
-
{children}
+type Props = {
+ classes?: string;
+}
+export const PreferencesSegment: FunctionComponent
= ({
+ children,
+ classes = ''
+ }) => (
+ {children}
);
diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx
index faf149cbd..732809ab4 100644
--- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx
+++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx
@@ -3,7 +3,7 @@ import {
SubscriptionWrapper,
Credentials,
SignOutWrapper,
- Authentication,
+ Authentication
} from '@/preferences/panes/account';
import { PreferencesPane } from '@/preferences/components';
import { observer } from 'mobx-react-lite';
@@ -22,7 +22,7 @@ export const AccountPreferences = observer(
return (
- {appState.enableUnfinishedFeatures && }
+
);
@@ -32,7 +32,7 @@ export const AccountPreferences = observer(
- {appState.enableUnfinishedFeatures && }
+
);
diff --git a/app/assets/javascripts/preferences/panes/ExtensionPane.tsx b/app/assets/javascripts/preferences/panes/ExtensionPane.tsx
new file mode 100644
index 000000000..ed8db4f35
--- /dev/null
+++ b/app/assets/javascripts/preferences/panes/ExtensionPane.tsx
@@ -0,0 +1,47 @@
+import { PreferencesGroup, PreferencesSegment } from "@/preferences/components";
+import { WebApplication } from "@/ui_models/application";
+import { SNComponent } from "@standardnotes/snjs/dist/@types";
+import { observer } from "mobx-react-lite";
+import { FunctionComponent } from "preact";
+import { ExtensionItem } from "./extensions-segments";
+import { ComponentView } from '@/components/ComponentView';
+import { AppState } from '@/ui_models/app_state';
+import { PreferencesMenu } from '@/preferences/PreferencesMenu';
+
+interface IProps {
+ application: WebApplication;
+ appState: AppState;
+ extension: SNComponent;
+ preferencesMenu: PreferencesMenu;
+}
+
+export const ExtensionPane: FunctionComponent = observer(
+ ({ extension, application, appState, preferencesMenu }) => {
+ const latestVersion = preferencesMenu.extensionsLatestVersions.getVersion(extension);
+
+ return (
+
+
+
+
+ application.deleteItem(extension).then(() => preferencesMenu.loadExtensionsPanes())}
+ toggleActivate={() => application.toggleComponent(extension).then(() => preferencesMenu.loadExtensionsPanes())}
+ latestVersion={latestVersion}
+ />
+
+
+
+
+
+
+
+ );
+ });
diff --git a/app/assets/javascripts/preferences/panes/Extensions.tsx b/app/assets/javascripts/preferences/panes/Extensions.tsx
index 20381385a..5bf8b8f01 100644
--- a/app/assets/javascripts/preferences/panes/Extensions.tsx
+++ b/app/assets/javascripts/preferences/panes/Extensions.tsx
@@ -5,59 +5,35 @@ import { WebApplication } from '@/ui_models/application';
import { FunctionComponent } from 'preact';
import {
Title,
- PreferencesGroup,
- PreferencesPane,
PreferencesSegment,
} from '../components';
-import { ConfirmCustomExtension, ExtensionItem } from './extensions-segments';
+import { ConfirmCustomExtension, ExtensionItem, ExtensionsLatestVersions } from './extensions-segments';
import { useEffect, useRef, useState } from 'preact/hooks';
-import { FeatureDescription } from '@standardnotes/features';
+import { observer } from 'mobx-react-lite';
const loadExtensions = (application: WebApplication) => application.getItems([
ContentType.ActionsExtension,
ContentType.Component,
ContentType.Theme,
-]) as SNComponent[];
-
-function collectFeatures(features: FeatureDescription[] | undefined, versionMap: Map) {
- if (features == undefined) return;
- for (const feature of features) {
- versionMap.set(feature.identifier, feature.version);
- }
-}
-
-const loadLatestVersions = (application: WebApplication) => application.getAvailableSubscriptions()
- .then(subscriptions => {
- const versionMap: Map = new Map();
- collectFeatures(subscriptions?.CORE_PLAN?.features, versionMap);
- collectFeatures(subscriptions?.PLUS_PLAN?.features, versionMap);
- collectFeatures(subscriptions?.PRO_PLAN?.features, versionMap);
- return versionMap;
- });
+], true) as SNComponent[];
export const Extensions: FunctionComponent<{
application: WebApplication
-}> = ({ application }) => {
+ extensionsLatestVersions: ExtensionsLatestVersions,
+}> = observer(({ application, extensionsLatestVersions }) => {
const [customUrl, setCustomUrl] = useState('');
const [confirmableExtension, setConfirmableExtension] = useState(undefined);
const [extensions, setExtensions] = useState(loadExtensions(application));
- const [latestVersions, setLatestVersions] = useState