diff --git a/app/assets/javascripts/preferences/PreferencesView.tsx b/app/assets/javascripts/preferences/PreferencesView.tsx
index d9059a440..74839c310 100644
--- a/app/assets/javascripts/preferences/PreferencesView.tsx
+++ b/app/assets/javascripts/preferences/PreferencesView.tsx
@@ -1,7 +1,7 @@
import { RoundIconButton } from '@/components/RoundIconButton';
import { TitleBar, Title } from '@/components/TitleBar';
import { FunctionComponent } from 'preact';
-import { AccountPreferences, General, HelpAndFeedback, Security } from './panes';
+import { AccountPreferences, HelpAndFeedback, Listed, General, Security } from './panes';
import { observer } from 'mobx-react-lite';
import { PreferencesMenu } from './PreferencesMenu';
import { PreferencesMenuView } from './PreferencesMenuView';
@@ -41,7 +41,7 @@ const PaneSelector: FunctionComponent<
/>
);
case 'listed':
- return null;
+ return ;
case 'shortcuts':
return null;
case 'accessibility':
diff --git a/app/assets/javascripts/preferences/components/Content.tsx b/app/assets/javascripts/preferences/components/Content.tsx
index a754c3d4d..fde262846 100644
--- a/app/assets/javascripts/preferences/components/Content.tsx
+++ b/app/assets/javascripts/preferences/components/Content.tsx
@@ -14,14 +14,15 @@ export const Text: FunctionComponent<{ className?: string }> = ({
}) =>
{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 mt-3 \
+border-1 border-gray-300 px-4 py-1.75 font-bold text-sm fit-content \
focus:bg-contrast hover:bg-contrast `;
-export const LinkButton: FunctionComponent<{ label: string; link: string }> = ({
- label,
- link,
-}) => (
-
+export const LinkButton: FunctionComponent<{
+ label: string;
+ link: string;
+ className?: string;
+}> = ({ label, link, className }) => (
+
{label}
);
diff --git a/app/assets/javascripts/preferences/panes/HelpFeedback.tsx b/app/assets/javascripts/preferences/panes/HelpFeedback.tsx
index 58063a33d..d2a1d0d19 100644
--- a/app/assets/javascripts/preferences/panes/HelpFeedback.tsx
+++ b/app/assets/javascripts/preferences/panes/HelpFeedback.tsx
@@ -52,7 +52,11 @@ export const HelpAndFeedback: FunctionComponent = () => (
Can’t find your question here?
-
+
@@ -68,6 +72,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
before advocating for a feature request.
@@ -82,6 +87,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
group for discussions on security, themes, editors and more.
@@ -93,7 +99,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
Send an email to help@standardnotes.com and we’ll sort it out.
-
+
diff --git a/app/assets/javascripts/preferences/panes/Listed.tsx b/app/assets/javascripts/preferences/panes/Listed.tsx
new file mode 100644
index 000000000..d1254c2af
--- /dev/null
+++ b/app/assets/javascripts/preferences/panes/Listed.tsx
@@ -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([]);
+ 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 (
+
+ {items.length > 0 && (
+
+
+
+ Your {items.length === 1 ? 'Blog' : 'Blogs'} on Listed
+
+
+ {items.map((item, index, array) => {
+ return (
+
+ );
+ })}
+
+
+ )}
+
+
+ About Listed
+
+ What is Listed?
+
+ Listed is a free blogging platform that allows you to create a
+ public journal published directly from your notes.{' '}
+
+ Learn more
+
+
+
+ {items.length === 0 ? (
+
+ How to get started?
+
+ First, you’ll need to sign up for Listed. Once you have your
+ Listed account, follow the instructions to connect it with your
+ Standard Notes account.
+
+
+
+ ) : null}
+
+
+ );
+});
diff --git a/app/assets/javascripts/preferences/panes/index.ts b/app/assets/javascripts/preferences/panes/index.ts
index da5721469..56e97df76 100644
--- a/app/assets/javascripts/preferences/panes/index.ts
+++ b/app/assets/javascripts/preferences/panes/index.ts
@@ -1,4 +1,5 @@
export * from './HelpFeedback';
export * from './Security';
export * from './AccountPreferences';
+export * from './Listed';
export * from './General';
diff --git a/app/assets/javascripts/preferences/panes/listed/BlogItem.tsx b/app/assets/javascripts/preferences/panes/listed/BlogItem.tsx
new file mode 100644
index 000000000..804a9c813
--- /dev/null
+++ b/app/assets/javascripts/preferences/panes/listed/BlogItem.tsx
@@ -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;
+ application: WebApplication;
+};
+
+export const BlogItem: FunctionalComponent = ({
+ item,
+ showSeparator,
+ disabled,
+ disconnect,
+ application,
+}) => {
+ const [actions, setActions] = useState([]);
+ 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 (
+ <>
+ {item?.name}
+
+ {isLoadingActions ? (
+
+ ) : null}
+ {actions && actions?.length > 0 ? (
+ <>
+
action.label === 'Open Blog')
+ ?.url || ''
+ }
+ />
+ action.label === 'Settings')
+ ?.url || ''
+ }
+ />
+
+ >
+ ) : null}
+
+ {showSeparator && }
+ >
+ );
+};