Merge branch 'develop' into feat/open-purchase-flow
This commit is contained in:
@@ -3,6 +3,7 @@ import { AppState } from '@/ui_models/app_state';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { PreferencesPane } from '../components';
|
||||
import { ErrorReporting } from './general-segments';
|
||||
import { Tools } from './general-segments/Tools';
|
||||
|
||||
interface GeneralProps {
|
||||
appState: AppState;
|
||||
@@ -11,6 +12,7 @@ interface GeneralProps {
|
||||
|
||||
export const General: FunctionComponent<GeneralProps> = (props) => (
|
||||
<PreferencesPane>
|
||||
<Tools application={props.application} />
|
||||
<ErrorReporting appState={props.appState} />
|
||||
</PreferencesPane>
|
||||
);
|
||||
|
||||
@@ -52,7 +52,11 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can’t 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 we’ll 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>
|
||||
|
||||
116
app/assets/javascripts/preferences/panes/Listed.tsx
Normal file
116
app/assets/javascripts/preferences/panes/Listed.tsx
Normal 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, 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.
|
||||
</Text>
|
||||
<LinkButton
|
||||
className="min-w-20 mt-3"
|
||||
link="https://listed.to"
|
||||
label="Get started"
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
) : null}
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
||||
import { Switch } from '@/components/Switch';
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/preferences/components';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { PrefKey } from '@standardnotes/snjs';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const Tools: FunctionalComponent<Props> = observer(
|
||||
({ application }: Props) => {
|
||||
const [monospaceFont, setMonospaceFont] = useState(() =>
|
||||
application.getPreference(PrefKey.EditorMonospaceEnabled)
|
||||
);
|
||||
const [marginResizers, setMarginResizers] = useState(() =>
|
||||
application.getPreference(PrefKey.EditorResizersEnabled)
|
||||
);
|
||||
const [spellcheck, setSpellcheck] = useState(() =>
|
||||
application.getPreference(PrefKey.EditorSpellcheck)
|
||||
);
|
||||
|
||||
const toggleMonospaceFont = () => {
|
||||
setMonospaceFont(!monospaceFont);
|
||||
application.setPreference(PrefKey.EditorMonospaceEnabled, !monospaceFont);
|
||||
};
|
||||
|
||||
const toggleMarginResizers = () => {
|
||||
setMarginResizers(!marginResizers);
|
||||
application.setPreference(PrefKey.EditorResizersEnabled, !marginResizers);
|
||||
};
|
||||
|
||||
const toggleSpellcheck = () => {
|
||||
setSpellcheck(!spellcheck);
|
||||
application.setPreference(PrefKey.EditorSpellcheck, !spellcheck);
|
||||
};
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Tools</Title>
|
||||
<div className="mt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Monospace Font</Subtitle>
|
||||
<Text>Toggles the font style in the Plain Text editor.</Text>
|
||||
</div>
|
||||
<Switch onChange={toggleMonospaceFont} checked={monospaceFont} />
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Margin Resizers</Subtitle>
|
||||
<Text>Allows left and right editor margins to be resized.</Text>
|
||||
</div>
|
||||
<Switch
|
||||
onChange={toggleMarginResizers}
|
||||
checked={marginResizers}
|
||||
/>
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Spellcheck</Subtitle>
|
||||
<Text>
|
||||
May degrade performance, especially with long notes. Available
|
||||
in the Plain Text editor and most specialty editors.
|
||||
</Text>
|
||||
</div>
|
||||
<Switch onChange={toggleSpellcheck} checked={spellcheck} />
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1 +1,2 @@
|
||||
export * from './ErrorReporting';
|
||||
export * from './Tools';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './HelpFeedback';
|
||||
export * from './Security';
|
||||
export * from './AccountPreferences';
|
||||
export * from './Listed';
|
||||
export * from './General';
|
||||
|
||||
110
app/assets/javascripts/preferences/panes/listed/BlogItem.tsx
Normal file
110
app/assets/javascripts/preferences/panes/listed/BlogItem.tsx
Normal 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" />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -15,7 +15,7 @@ const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(({
|
||||
const archived = formatCount(count.archived, 'archived notes');
|
||||
const deleted = formatCount(count.deleted, 'trashed notes');
|
||||
|
||||
const checkIcon = <Icon className="success min-w-5 min-h-5" type="check-bold" />;
|
||||
const checkIcon = <Icon className="success min-w-4 min-h-4" type="check-bold" />;
|
||||
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />;
|
||||
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />;
|
||||
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />;
|
||||
|
||||
@@ -16,9 +16,8 @@ const DisclosureIconButton: FunctionComponent<{
|
||||
<DisclosureButton
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${
|
||||
className ?? ''
|
||||
}`}
|
||||
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${className ?? ''
|
||||
}`}
|
||||
>
|
||||
<Icon type={icon} />
|
||||
</DisclosureButton>
|
||||
@@ -58,7 +57,7 @@ export const AuthAppInfoTooltip: FunctionComponent = () => {
|
||||
/>
|
||||
<DisclosurePanel>
|
||||
<div
|
||||
className={`bg-black color-white text-center rounded shadow-overlay
|
||||
className={`bg-inverted-default color-inverted-default text-center rounded shadow-overlay
|
||||
py-1.5 px-2 absolute w-103 -top-10 -left-51`}
|
||||
>
|
||||
Some apps, like Google Authenticator, do not back up and restore
|
||||
|
||||
@@ -3,5 +3,5 @@ import { FunctionComponent } from 'preact';
|
||||
export const Bullet: FunctionComponent<{ className?: string }> = ({
|
||||
className = '',
|
||||
}) => (
|
||||
<div className={`min-w-1 min-h-1 rounded-full bg-black ${className} mr-2`} />
|
||||
<div className={`min-w-1 min-h-1 rounded-full bg-inverted-default ${className} mr-2`} />
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user