feat: Add Listed pane in preferences (#651)

* feat: Add Listed pane in preferences

* feat: Add list of blogs in Listed preferences

feat: Allow custom classnames in LinkButton

feat: Add mt-0 class

* fix: Don't show non-Listed Action Extensions

* fix: Use streamItems()

* fix: Re-render UI when item is deleted

* feat: Remove hardcoded margin-top for LinkButton

* fix: Fix ESLint exhaustive-deps error

* fix: Use useCallback hook

feat: Disconnect shows state "Disconnecting..." when deleting item

* fix: Remove unused imports

* fix: Simplify disconnect function

fix: Use key in the correct place

* feat: Add confirmation dialog when deleting a blog

feat: Show Blog/Blogs in the title depending on the
number of items

* style: Revert file to original formatting

* refactor: Use preact instead of react

refactor: Use FunctionalComponent type

* feat: Show alert when disconnecting errors out

fix: Set state to false even if errors

refactor: Use ternary operator for Getting
Started section

* feat: Load Listed blog actions asynchronously

* feat: Only fetch actions if not already available

* refactor: Use async/await for disconnecting

Co-authored-by: Mo Bitar <mo@standardnotes.org>
This commit is contained in:
Aman Harwara
2021-09-29 21:32:30 +05:30
committed by GitHub
parent 0e5692d8ab
commit e72d737220
6 changed files with 244 additions and 10 deletions

View File

@@ -52,7 +52,11 @@ export const HelpAndFeedback: FunctionComponent = () => (
</PreferencesSegment>
<PreferencesSegment>
<Subtitle>Cant find your question here?</Subtitle>
<LinkButton label="Open FAQ" link="https://standardnotes.com/help" />
<LinkButton
className="mt-3"
label="Open FAQ"
link="https://standardnotes.com/help"
/>
</PreferencesSegment>
</PreferencesGroup>
<PreferencesGroup>
@@ -68,6 +72,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
before advocating for a feature request.
</Text>
<LinkButton
className="mt-3"
label="Go to the forum"
link="https://forum.standardnotes.org/"
/>
@@ -82,6 +87,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
group for discussions on security, themes, editors and more.
</Text>
<LinkButton
className="mt-3"
link="https://standardnotes.com/slack"
label="Join our Slack group"
/>
@@ -93,7 +99,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
<Text>
Send an email to help@standardnotes.com and well sort it out.
</Text>
<LinkButton link="mailto: help@standardnotes.com" label="Email us" />
<LinkButton className="mt-3" link="mailto: help@standardnotes.com" label="Email us" />
</PreferencesSegment>
</PreferencesGroup>
</PreferencesPane>

View File

@@ -0,0 +1,116 @@
import {
PreferencesGroup,
PreferencesPane,
PreferencesSegment,
Title,
Subtitle,
Text,
LinkButton,
} from '../components';
import { observer } from 'mobx-react-lite';
import { WebApplication } from '@/ui_models/application';
import { ContentType, SNComponent } 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';
type Props = {
application: WebApplication;
};
export const Listed = observer(({ application }: Props) => {
const [items, setItems] = useState<SNComponent[]>([]);
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[];
setItems(components);
}, [application]);
useEffect(() => {
reloadItems();
}, [reloadItems]);
const disconnectListedBlog = (item: SNItem) => {
return new Promise((resolve, reject) => {
setIsDeleting(true);
application
.deleteItem(item)
.then(() => {
reloadItems();
setIsDeleting(false);
resolve(true);
})
.catch((err) => {
application.alertService.alert(err);
setIsDeleting(false);
console.error(err);
reject(err);
});
});
};
return (
<PreferencesPane>
{items.length > 0 && (
<PreferencesGroup>
<PreferencesSegment>
<Title>
Your {items.length === 1 ? 'Blog' : 'Blogs'} on Listed
</Title>
<div className="h-2 w-full" />
{items.map((item, index, array) => {
return (
<BlogItem
item={item}
showSeparator={index !== array.length - 1}
disabled={isDeleting}
disconnect={disconnectListedBlog}
key={item.uuid}
application={application}
/>
);
})}
</PreferencesSegment>
</PreferencesGroup>
)}
<PreferencesGroup>
<PreferencesSegment>
<Title>About Listed</Title>
<div className="h-2 w-full" />
<Subtitle>What is Listed?</Subtitle>
<Text>
Listed is a free blogging platform that allows you to create a
public journal published directly from your notes.{' '}
<a
target="_blank"
href="https://listed.to"
rel="noreferrer noopener"
>
Learn more
</a>
</Text>
</PreferencesSegment>
{items.length === 0 ? (
<PreferencesSegment>
<Subtitle>How to get started?</Subtitle>
<Text>
First, youll need to sign up for Listed. Once you have your
Listed account, follow the instructions to connect it with your
Standard Notes account.
</Text>
<LinkButton
className="min-w-20 mt-3"
link="https://listed.to"
label="Get started"
/>
</PreferencesSegment>
) : null}
</PreferencesGroup>
</PreferencesPane>
);
});

View File

@@ -1,4 +1,5 @@
export * from './HelpFeedback';
export * from './Security';
export * from './AccountPreferences';
export * from './Listed';
export * from './General';

View File

@@ -0,0 +1,110 @@
import { Button } from '@/components/Button';
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
import { LinkButton, Subtitle } from '@/preferences/components';
import { WebApplication } from '@/ui_models/application';
import {
Action,
ButtonType,
SNActionsExtension,
SNComponent,
SNItem,
} from '@standardnotes/snjs';
import { FunctionalComponent } from 'preact';
import { useEffect, useState } from 'preact/hooks';
type Props = {
item: SNComponent;
showSeparator: boolean;
disabled: boolean;
disconnect: (item: SNItem) => Promise<unknown>;
application: WebApplication;
};
export const BlogItem: FunctionalComponent<Props> = ({
item,
showSeparator,
disabled,
disconnect,
application,
}) => {
const [actions, setActions] = useState<Action[] | undefined>([]);
const [isLoadingActions, setIsLoadingActions] = useState(false);
const [isDisconnecting, setIsDisconnecting] = useState(false);
useEffect(() => {
const loadActions = async () => {
setIsLoadingActions(true);
application.actionsManager
.loadExtensionInContextOfItem(item as SNActionsExtension, item)
.then((extension) => {
setActions(extension?.actions);
})
.catch((err) => application.alertService.alert(err))
.finally(() => {
setIsLoadingActions(false);
});
};
if (!actions || actions.length === 0) loadActions();
}, [application.actionsManager, application.alertService, item, actions]);
const handleDisconnect = () => {
setIsDisconnecting(true);
application.alertService
.confirm(
'Disconnecting will result in loss of access to your blog. Ensure your Listed author key is backed up before uninstalling.',
`Disconnect blog "${item?.name}"?`,
'Disconnect',
ButtonType.Danger
)
.then(async (shouldDisconnect) => {
if (shouldDisconnect) {
await disconnect(item as SNItem);
}
})
.catch((err) => {
console.error(err);
application.alertService.alert(err);
})
.finally(() => {
setIsDisconnecting(false);
});
};
return (
<>
<Subtitle>{item?.name}</Subtitle>
<div className="flex">
{isLoadingActions ? (
<div className="sk-spinner small info"></div>
) : null}
{actions && actions?.length > 0 ? (
<>
<LinkButton
className="mr-2"
label="Open Blog"
link={
actions?.find((action: Action) => action.label === 'Open Blog')
?.url || ''
}
/>
<LinkButton
className="mr-2"
label="Settings"
link={
actions?.find((action: Action) => action.label === 'Settings')
?.url || ''
}
/>
<Button
type="danger"
label={isDisconnecting ? 'Disconnecting...' : 'Disconnect'}
disabled={disabled}
onClick={handleDisconnect}
/>
</>
) : null}
</div>
{showSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
</>
);
};