refactor: migrate remaining angular components to react (#833)

* refactor: menuRow directive to MenuRow component

* refactor: migrate footer to react

* refactor: migrate actions menu to react

* refactor: migrate history menu to react

* fix: click outside handler use capture to trigger event before re-render occurs which would otherwise cause node.contains to return incorrect result (specifically for the account menu)

* refactor: migrate revision preview modal to react

* refactor: migrate permissions modal to react

* refactor: migrate password wizard to react

* refactor: remove unused input modal directive

* refactor: remove unused delay hide component

* refactor: remove unused filechange directive

* refactor: remove unused elemReady directive

* refactor: remove unused sn-enter directive

* refactor: remove unused lowercase directive

* refactor: remove unused autofocus directive

* refactor(wip): note view to react

* refactor: use mutation observer to deinit textarea listeners

* refactor: migrate challenge modal to react

* refactor: migrate note group view to react

* refactor(wip): migrate remaining classes

* fix: navigation parent ref

* refactor: fully remove angular assets

* fix: account switcher

* fix: application view state

* refactor: remove unused password wizard type

* fix: revision preview and permissions modal

* fix: remove angular comment

* refactor: react panel resizers for editor

* feat: simple panel resizer

* fix: use simple panel resizer everywhere

* fix: simplify panel resizer state

* chore: rename simple panel resizer to panel resizer

* refactor: simplify column layout

* fix: editor mount safety check

* fix: use inline onLoad callback for iframe, as setting onload after it loads will never call it

* chore: fix note view test

* chore(deps): upgrade snjs
This commit is contained in:
Mo
2022-01-30 19:01:30 -06:00
committed by GitHub
parent 0ecbde6bac
commit 50c92619ce
117 changed files with 4715 additions and 5309 deletions

View File

@@ -1,7 +1,10 @@
import { FunctionComponent } from 'preact';
export const Title: FunctionComponent = ({ children }) => (
<h2 className="text-base m-0 mb-1">{children}</h2>
<>
<h2 className="text-base m-0 mb-1">{children}</h2>
<div className="min-h-2" />
</>
);
export const Subtitle: FunctionComponent<{ className?: string }> = ({

View File

@@ -1,9 +0,0 @@
import { toDirective } from '../components/utils';
import {
PreferencesViewWrapper,
PreferencesViewWrapperProps,
} from './PreferencesViewWrapper';
export const PreferencesDirective = toDirective<PreferencesViewWrapperProps>(
PreferencesViewWrapper
);

View File

@@ -74,6 +74,7 @@ export const Extensions: FunctionComponent<{
const confirmExtension = async () => {
await application.insertItem(confirmableExtension as SNComponent);
application.sync();
setExtensions(loadExtensions(application));
};
@@ -109,7 +110,6 @@ export const Extensions: FunctionComponent<{
{!confirmableExtension && (
<PreferencesSegment>
<Title>Install Custom Extension</Title>
<div className="min-h-2" />
<DecoratedInput
placeholder={'Enter Extension URL'}
text={customUrl}

View File

@@ -9,7 +9,7 @@ import {
} from '../components';
import { observer } from 'mobx-react-lite';
import { WebApplication } from '@/ui_models/application';
import { ContentType, SNComponent } from '@standardnotes/snjs';
import { ContentType, SNActionsExtension } from '@standardnotes/snjs';
import { SNItem } from '@standardnotes/snjs/dist/@types/models/core/item';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { BlogItem } from './listed/BlogItem';
@@ -19,15 +19,15 @@ type Props = {
};
export const Listed = observer(({ application }: Props) => {
const [items, setItems] = useState<SNComponent[]>([]);
const [items, setItems] = useState<SNActionsExtension[]>([]);
const [isDeleting, setIsDeleting] = useState(false);
const reloadItems = useCallback(() => {
const components = application
.getItems(ContentType.ActionsExtension)
.filter(
(item) => (item as SNComponent).package_info?.name === 'Listed'
) as SNComponent[];
.filter((item) =>
(item as SNActionsExtension).url.includes('listed')
) as SNActionsExtension[];
setItems(components);
}, [application]);

View File

@@ -1,68 +1,80 @@
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/preferences/components';
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/preferences/components';
import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { observer } from '@node_modules/mobx-react-lite';
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
import { dateToLocalizedString } from '@standardnotes/snjs';
import { useState } from 'preact/hooks';
import { useCallback, useState } from 'preact/hooks';
import { ChangeEmail } from '@/preferences/panes/account/changeEmail';
import { PasswordWizardType } from '@/types';
import { FunctionComponent } from 'preact';
import { FunctionComponent, render } from 'preact';
import { AppState } from '@/ui_models/app_state';
import { PasswordWizard } from '@/components/PasswordWizard';
type Props = {
application: WebApplication;
appState: AppState;
};
export const Credentials: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] = useState(false);
export const Credentials: FunctionComponent<Props> = observer(
({ application }: Props) => {
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] =
useState(false);
const user = application.getUser();
const user = application.getUser();
const passwordCreatedAtTimestamp = application.getUserPasswordCreationDate() as Date;
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp);
const passwordCreatedAtTimestamp =
application.getUserPasswordCreationDate() as Date;
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp);
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Credentials</Title>
<div className={'text-input mt-2'}>
Email
</div>
<Text>
You're signed in as <span className='font-bold'>{user?.email}</span>
</Text>
<Button
className='min-w-20 mt-3'
type='normal'
label='Change email'
onClick={() => {
setIsChangeEmailDialogOpen(true);
}}
/>
<HorizontalSeparator classes='mt-5 mb-3' />
<div className={'text-input mt-2'}>
Password
</div>
<Text>
Current password was set on <span className='font-bold'>{passwordCreatedOn}</span>
</Text>
<Button
className='min-w-20 mt-3'
type='normal'
label='Change password'
onClick={() => {
application.presentPasswordWizard(PasswordWizardType.ChangePassword);
}}
/>
{isChangeEmailDialogOpen && (
<ChangeEmail
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
application={application}
const presentPasswordWizard = useCallback(() => {
render(
<PasswordWizard application={application} />,
document.body.appendChild(document.createElement('div'))
);
}, [application]);
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Credentials</Title>
<Subtitle>Email</Subtitle>
<Text>
You're signed in as <span className="font-bold">{user?.email}</span>
</Text>
<Button
className="min-w-20 mt-3"
type="normal"
label="Change email"
onClick={() => {
setIsChangeEmailDialogOpen(true);
}}
/>
)}
</PreferencesSegment>
</PreferencesGroup>
);
});
<HorizontalSeparator classes="mt-5 mb-3" />
<Subtitle>Password</Subtitle>
<Text>
Current password was set on{' '}
<span className="font-bold">{passwordCreatedOn}</span>
</Text>
<Button
className="min-w-20 mt-3"
type="normal"
label="Change password"
onClick={presentPasswordWizard}
/>
{isChangeEmailDialogOpen && (
<ChangeEmail
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
application={application}
/>
)}
</PreferencesSegment>
</PreferencesGroup>
);
}
);

View File

@@ -22,7 +22,6 @@ const SignOutView: FunctionComponent<{
<PreferencesGroup>
<PreferencesSegment>
<Title>Sign out</Title>
<div className="min-h-2" />
<Subtitle>Other devices</Subtitle>
<Text>Want to sign out on all devices except this one?</Text>
<div className="min-h-3" />
@@ -74,7 +73,6 @@ const ClearSessionDataView: FunctionComponent<{
<PreferencesGroup>
<PreferencesSegment>
<Title>Clear session data</Title>
<div className="min-h-2" />
<Text>This will delete all local items and preferences.</Text>
<div className="min-h-3" />
<Button

View File

@@ -1,86 +1,113 @@
import { FunctionComponent } from "preact";
import { SNComponent } from "@standardnotes/snjs";
import { PreferencesSegment, SubtitleLight, Title } from "@/preferences/components";
import { Switch } from "@/components/Switch";
import { WebApplication } from "@/ui_models/application";
import { useState } from "preact/hooks";
import { Button } from "@/components/Button";
import { RenameExtension } from "./RenameExtension";
import { FunctionComponent } from 'preact';
import { SNComponent } from '@standardnotes/snjs';
import {
PreferencesSegment,
SubtitleLight,
Title,
} from '@/preferences/components';
import { Switch } from '@/components/Switch';
import { WebApplication } from '@/ui_models/application';
import { useState } from 'preact/hooks';
import { Button } from '@/components/Button';
import { RenameExtension } from './RenameExtension';
const UseHosted: FunctionComponent<{
offlineOnly: boolean, toggleOfllineOnly: () => void
offlineOnly: boolean;
toggleOfllineOnly: () => void;
}> = ({ offlineOnly, toggleOfllineOnly }) => (
<div className="flex flex-row">
<SubtitleLight className="flex-grow">Use hosted when local is unavailable</SubtitleLight>
<SubtitleLight className="flex-grow">
Use hosted when local is unavailable
</SubtitleLight>
<Switch onChange={toggleOfllineOnly} checked={!offlineOnly} />
</div>
);
export interface ExtensionItemProps {
application: WebApplication,
extension: SNComponent,
first: boolean,
latestVersion: string | undefined,
uninstall: (extension: SNComponent) => void,
toggleActivate?: (extension: SNComponent) => void,
application: WebApplication;
extension: SNComponent;
first: boolean;
latestVersion: string | undefined;
uninstall: (extension: SNComponent) => void;
toggleActivate?: (extension: SNComponent) => void;
}
export const ExtensionItem: FunctionComponent<ExtensionItemProps> =
({ application, extension, first, uninstall}) => {
const [offlineOnly, setOfflineOnly] = useState(extension.offlineOnly ?? false);
const [extensionName, setExtensionName] = useState(extension.name);
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
application,
extension,
first,
uninstall,
}) => {
const [offlineOnly, setOfflineOnly] = useState(
extension.offlineOnly ?? false
);
const [extensionName, setExtensionName] = useState(extension.name);
const toggleOffllineOnly = () => {
const newOfflineOnly = !offlineOnly;
setOfflineOnly(newOfflineOnly);
application
.changeAndSaveItem(extension.uuid, (m: any) => {
if (m.content == undefined) m.content = {};
m.content.offlineOnly = newOfflineOnly;
})
.then((item) => {
const component = (item as SNComponent);
setOfflineOnly(component.offlineOnly);
})
.catch(e => {
console.error(e);
});
};
const changeExtensionName = (newName: string) => {
setExtensionName(newName);
application
.changeAndSaveItem(extension.uuid, (m: any) => {
if (m.content == undefined) m.content = {};
m.content.name = newName;
})
.then((item) => {
const component = (item as SNComponent);
setExtensionName(component.name);
});
};
const localInstallable = extension.package_info.download_url;
const isThirParty = application.isThirdPartyFeature(extension.identifier);
return (
<PreferencesSegment classes={'mb-5'}>
{first && <>
<Title>Extensions</Title>
<div className="w-full min-h-3" />
</>}
<RenameExtension extensionName={extensionName} changeName={changeExtensionName} />
<div className="min-h-2" />
{isThirParty && localInstallable && <UseHosted offlineOnly={offlineOnly} toggleOfllineOnly={toggleOffllineOnly} />}
<>
<div className="min-h-2" />
<div className="flex flex-row">
<Button className="min-w-20" type="normal" label="Uninstall" onClick={() => uninstall(extension)} />
</div>
</>
</PreferencesSegment >
);
const toggleOffllineOnly = () => {
const newOfflineOnly = !offlineOnly;
setOfflineOnly(newOfflineOnly);
application
.changeAndSaveItem(extension.uuid, (m: any) => {
if (m.content == undefined) m.content = {};
m.content.offlineOnly = newOfflineOnly;
})
.then((item) => {
const component = item as SNComponent;
setOfflineOnly(component.offlineOnly);
})
.catch((e) => {
console.error(e);
});
};
const changeExtensionName = (newName: string) => {
setExtensionName(newName);
application
.changeAndSaveItem(extension.uuid, (m: any) => {
if (m.content == undefined) m.content = {};
m.content.name = newName;
})
.then((item) => {
const component = item as SNComponent;
setExtensionName(component.name);
});
};
const localInstallable = extension.package_info.download_url;
const isThirParty = application.isThirdPartyFeature(extension.identifier);
return (
<PreferencesSegment classes={'mb-5'}>
{first && (
<>
<Title>Extensions</Title>
</>
)}
<RenameExtension
extensionName={extensionName}
changeName={changeExtensionName}
/>
<div className="min-h-2" />
{isThirParty && localInstallable && (
<UseHosted
offlineOnly={offlineOnly}
toggleOfllineOnly={toggleOffllineOnly}
/>
)}
<>
<div className="min-h-2" />
<div className="flex flex-row">
<Button
className="min-w-20"
type="normal"
label="Uninstall"
onClick={() => uninstall(extension)}
/>
</div>
</>
</PreferencesSegment>
);
};

View File

@@ -148,7 +148,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
<PreferencesGroup>
<PreferencesSegment>
<Title>Defaults</Title>
<div className="mt-2">
<div>
<Subtitle>Default Editor</Subtitle>
<Text>New notes will be created using this editor.</Text>
<div className="mt-2">
@@ -166,8 +166,9 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
<div className="flex flex-col">
<Subtitle>Spellcheck</Subtitle>
<Text>
The default spellcheck value for new notes. Spellcheck can be configured per note from the note context menu.
Spellcheck may degrade overall typing performance with long notes.
The default spellcheck value for new notes. Spellcheck can be
configured per note from the note context menu. Spellcheck may
degrade overall typing performance with long notes.
</Text>
</div>
<Switch onChange={toggleSpellcheck} checked={spellcheck} />

View File

@@ -40,7 +40,7 @@ export const Tools: FunctionalComponent<Props> = observer(
<PreferencesGroup>
<PreferencesSegment>
<Title>Tools</Title>
<div className="mt-2">
<div>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Monospace Font</Subtitle>

View File

@@ -6,14 +6,13 @@ import {
Action,
ButtonType,
SNActionsExtension,
SNComponent,
SNItem,
} from '@standardnotes/snjs';
import { FunctionalComponent } from 'preact';
import { useEffect, useState } from 'preact/hooks';
type Props = {
item: SNComponent;
item: SNActionsExtension;
showSeparator: boolean;
disabled: boolean;
disconnect: (item: SNItem) => Promise<unknown>;
@@ -35,7 +34,7 @@ export const BlogItem: FunctionalComponent<Props> = ({
const loadActions = async () => {
setIsLoadingActions(true);
application.actionsManager
.loadExtensionInContextOfItem(item as SNActionsExtension, item)
.loadExtensionInContextOfItem(item, item)
.then((extension) => {
setActions(extension?.actions);
})