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 || '' + } + /> +
+ {showSeparator && } + + ); +};