Merge branch 'develop' into feat/open-purchase-flow

This commit is contained in:
Antonella Sgarlatta
2021-10-04 17:50:21 -03:00
committed by GitHub
31 changed files with 463 additions and 210 deletions

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 16.0001C7.91675 16.0001 6.07508 14.9334 5.00008 13.3334C5.02508 11.6667 8.33342 10.7501 10.0001 10.7501C11.6667 10.7501 14.9751 11.6667 15.0001 13.3334C13.9251 14.9334 12.0834 16.0001 10.0001 16.0001ZM10.0001 4.16675C10.6631 4.16675 11.299 4.43014 11.7679 4.89898C12.2367 5.36782 12.5001 6.00371 12.5001 6.66675C12.5001 7.32979 12.2367 7.96568 11.7679 8.43452C11.299 8.90336 10.6631 9.16675 10.0001 9.16675C9.33704 9.16675 8.70116 8.90336 8.23232 8.43452C7.76347 7.96568 7.50008 7.32979 7.50008 6.66675C7.50008 6.00371 7.76347 5.36782 8.23232 4.89898C8.70116 4.43014 9.33704 4.16675 10.0001 4.16675ZM10.0001 1.66675C8.90573 1.66675 7.8221 1.8823 6.81105 2.30109C5.80001 2.71987 4.88135 3.3337 4.10753 4.10753C2.54472 5.67033 1.66675 7.78995 1.66675 10.0001C1.66675 12.2102 2.54472 14.3298 4.10753 15.8926C4.88135 16.6665 5.80001 17.2803 6.81105 17.6991C7.8221 18.1179 8.90573 18.3334 10.0001 18.3334C12.2102 18.3334 14.3298 17.4554 15.8926 15.8926C17.4554 14.3298 18.3334 12.2102 18.3334 10.0001C18.3334 5.39175 14.5834 1.66675 10.0001 1.66675Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.49995 17.0167L2.32495 11.8417L4.68328 9.48332L7.49995 12.3083L15.7333 4.06665L18.0916 6.42498L7.49995 17.0167Z" /> <path d="M7.49995 17.0167L2.32495 11.8417L4.68328 9.48332L7.49995 12.3083L15.7333 4.06665L18.0916 6.42498L7.49995 17.0167Z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 207 B

View File

@@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5001 5.83345L7.50008 15.8334L2.91675 11.2501L4.09175 10.0751L7.50008 13.4751L16.3251 4.65845L17.5001 5.83345Z"/> <path d="M17.5001 5.83345L7.50008 15.8334L2.91675 11.2501L4.09175 10.0751L7.50008 13.4751L16.3251 4.65845L17.5001 5.83345Z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 206 B

View File

@@ -1,6 +1,5 @@
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { PasswordWizardType } from '@/types';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; import { User } from '@standardnotes/snjs/dist/@types/services/api/responses';
@@ -10,22 +9,12 @@ type Props = {
} }
const User = observer(({ const User = observer(({
appState, appState,
application, application,
}: Props) => { }: Props) => {
const { server, closeAccountMenu } = appState.accountMenu; const { server } = appState.accountMenu;
const user = application.getUser(); const user = application.getUser();
const openPasswordWizard = () => {
closeAccountMenu();
application.presentPasswordWizard(PasswordWizardType.ChangePassword);
};
const openSessionsModal = () => {
closeAccountMenu();
appState.openSessionsModal();
};
return ( return (
<div className="sk-panel-section"> <div className="sk-panel-section">
{appState.sync.errorMessage && ( {appState.sync.errorMessage && (
@@ -56,12 +45,6 @@ const User = observer(({
</div> </div>
</div> </div>
<div className="sk-panel-row" /> <div className="sk-panel-row" />
<a className="sk-a info sk-panel-row condensed" onClick={openPasswordWizard}>
Change Password
</a>
<a className="sk-a info sk-panel-row condensed" onClick={openSessionsModal}>
Manage Sessions
</a>
</div> </div>
); );
}); });

View File

@@ -6,11 +6,6 @@ import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal';
import Authentication from '@/components/AccountMenu/Authentication'; import Authentication from '@/components/AccountMenu/Authentication';
import Footer from '@/components/AccountMenu/Footer'; import Footer from '@/components/AccountMenu/Footer';
import User from '@/components/AccountMenu/User'; import User from '@/components/AccountMenu/User';
import Encryption from '@/components/AccountMenu/Encryption';
import Protections from '@/components/AccountMenu/Protections';
import PasscodeLock from '@/components/AccountMenu/PasscodeLock';
import DataBackup from '@/components/AccountMenu/DataBackup';
import ErrorReporting from '@/components/AccountMenu/ErrorReporting';
import { useEffect } from 'preact/hooks'; import { useEffect } from 'preact/hooks';
type Props = { type Props = {
@@ -51,25 +46,12 @@ const AccountMenu = observer(({ application, appState }: Props) => {
application={application} application={application}
appState={appState} appState={appState}
/> />
{!showLogin && !showRegister && ( {!showLogin && !showRegister && user && (
<div> <div>
{user && ( <User
<User
application={application}
appState={appState}
/>
)}
<Encryption appState={appState} />
<Protections application={application} />
<PasscodeLock
application={application} application={application}
appState={appState} appState={appState}
/> />
<DataBackup
application={application}
appState={appState}
/>
<ErrorReporting appState={appState} />
</div> </div>
)} )}
</div> </div>

View File

@@ -9,9 +9,9 @@ const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content`;
type ButtonType = 'normal' | 'primary' | 'danger'; type ButtonType = 'normal' | 'primary' | 'danger';
const buttonClasses: { [type in ButtonType]: string } = { const buttonClasses: { [type in ButtonType]: string } = {
normal: `${baseClass} bg-default color-text border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`, normal: `${baseClass} bg-default color-text border-neutral border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`,
primary: `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 focus:brightness-130`, primary: `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 focus:brightness-130`,
danger: `${baseClass} bg-default color-danger border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`, danger: `${baseClass} bg-default color-danger border-neutral border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`,
}; };
export const Button: FunctionComponent<{ export const Button: FunctionComponent<{

View File

@@ -28,13 +28,13 @@ export const DecoratedInput: FunctionalComponent<Props> = ({
autocomplete = false, autocomplete = false,
}) => { }) => {
const baseClasses = const baseClasses =
'rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center'; 'rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center bg-contrast';
const stateClasses = disabled const stateClasses = disabled
? 'no-border bg-grey-5' ? 'no-border'
: 'border-solid border-1 border-gray-300'; : 'border-solid border-1 border-gray-300';
const classes = `${baseClasses} ${stateClasses} ${className}`; const classes = `${baseClasses} ${stateClasses} ${className}`;
const inputBaseClasses = 'w-full no-border color-black focus:shadow-none'; const inputBaseClasses = 'w-full no-border color-text focus:shadow-none bg-contrast';
const inputStateClasses = disabled ? 'overflow-ellipsis' : ''; const inputStateClasses = disabled ? 'overflow-ellipsis' : '';
return ( return (
<div className={`${classes} focus-within:ring-info`}> <div className={`${classes} focus-within:ring-info`}>

View File

@@ -28,6 +28,7 @@ import DownloadIcon from '../../icons/ic-download.svg';
import InfoIcon from '../../icons/ic-info.svg'; import InfoIcon from '../../icons/ic-info.svg';
import CheckIcon from '../../icons/ic-check.svg'; import CheckIcon from '../../icons/ic-check.svg';
import CheckBoldIcon from '../../icons/ic-check-bold.svg'; import CheckBoldIcon from '../../icons/ic-check-bold.svg';
import AccountCircleIcon from '../../icons/ic-account-circle.svg';
import { toDirective } from './utils'; import { toDirective } from './utils';
import { FunctionalComponent } from 'preact'; import { FunctionalComponent } from 'preact';
@@ -61,7 +62,8 @@ const ICONS = {
download: DownloadIcon, download: DownloadIcon,
info: InfoIcon, info: InfoIcon,
check: CheckIcon, check: CheckIcon,
"check-bold": CheckBoldIcon, 'check-bold': CheckBoldIcon,
'account-circle': AccountCircleIcon,
}; };
export type IconType = keyof typeof ICONS; export type IconType = keyof typeof ICONS;

View File

@@ -11,9 +11,9 @@ export const Input: FunctionalComponent<Props> = ({
disabled = false, disabled = false,
text, text,
}) => { }) => {
const base = `rounded py-1.5 px-3 text-input my-1 h-8`; const base = `rounded py-1.5 px-3 text-input my-1 h-8 bg-contrast`;
const stateClasses = disabled const stateClasses = disabled
? 'no-border bg-grey-5' ? 'no-border'
: 'border-solid border-1 border-gray-300'; : 'border-solid border-1 border-gray-300';
const classes = `${base} ${stateClasses} ${className}`; const classes = `${base} ${stateClasses} ${className}`;
return ( return (

View File

@@ -33,11 +33,11 @@ export const ModalDialogLabel: FunctionComponent<{
}> = ({ children, closeDialog }) => ( }> = ({ children, closeDialog }) => (
<AlertDialogLabel className=""> <AlertDialogLabel className="">
<div className="px-4 py-4 flex flex-row items-center"> <div className="px-4 py-4 flex flex-row items-center">
<div className="flex-grow color-black text-lg font-bold">{children}</div> <div className="flex-grow color-text text-lg font-bold">{children}</div>
<IconButton <IconButton
focusable={true} focusable={true}
title="Close" title="Close"
className="color-grey-1 h-5 w-5" className="color-neutral h-5 w-5"
icon="close" icon="close"
onClick={() => closeDialog()} onClick={() => closeDialog()}
/> />
@@ -61,11 +61,11 @@ export const ModalDialogButtons: FunctionComponent = ({ children }) => (
<div className="px-4 py-4 flex flex-row justify-end items-center"> <div className="px-4 py-4 flex flex-row justify-end items-center">
{children != undefined && Array.isArray(children) {children != undefined && Array.isArray(children)
? children.map((child, idx, arr) => ( ? children.map((child, idx, arr) => (
<> <>
{child} {child}
{idx < arr.length - 1 ? <div className="min-w-3" /> : undefined} {idx < arr.length - 1 ? <div className="min-w-3" /> : undefined}
</> </>
)) ))
: children} : children}
</div> </div>
</> </>

View File

@@ -1,7 +1,7 @@
import { RoundIconButton } from '@/components/RoundIconButton'; import { RoundIconButton } from '@/components/RoundIconButton';
import { TitleBar, Title } from '@/components/TitleBar'; import { TitleBar, Title } from '@/components/TitleBar';
import { FunctionComponent } from 'preact'; 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 { observer } from 'mobx-react-lite';
import { PreferencesMenu } from './PreferencesMenu'; import { PreferencesMenu } from './PreferencesMenu';
import { PreferencesMenuView } from './PreferencesMenuView'; import { PreferencesMenuView } from './PreferencesMenuView';
@@ -41,7 +41,7 @@ const PaneSelector: FunctionComponent<
/> />
); );
case 'listed': case 'listed':
return null; return <Listed application={props.application} />;
case 'shortcuts': case 'shortcuts':
return null; return null;
case 'accessibility': case 'accessibility':

View File

@@ -14,14 +14,15 @@ export const Text: FunctionComponent<{ className?: string }> = ({
}) => <p className={`${className} text-xs`}>{children}</p>; }) => <p className={`${className} text-xs`}>{children}</p>;
const buttonClasses = `block bg-default color-text rounded border-solid \ 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 `; focus:bg-contrast hover:bg-contrast border-neutral`;
export const LinkButton: FunctionComponent<{ label: string; link: string }> = ({ export const LinkButton: FunctionComponent<{
label, label: string;
link, link: string;
}) => ( className?: string;
<a target="_blank" className={buttonClasses} href={link}> }> = ({ label, link, className }) => (
<a target="_blank" className={`${className} ${buttonClasses}`} href={link}>
{label} {label}
</a> </a>
); );

View File

@@ -15,7 +15,7 @@ export const MenuItem: FunctionComponent<Props> = ({
onClick, onClick,
}) => ( }) => (
<div <div
className={`preferences-menu-item ${selected ? 'selected' : ''}`} className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
onClick(); onClick();

View File

@@ -1,7 +1,7 @@
import { FunctionComponent } from 'preact'; import { FunctionComponent } from 'preact';
export const PreferencesPane: FunctionComponent = ({ children }) => ( export const PreferencesPane: FunctionComponent = ({ children }) => (
<div className="color-black flex-grow flex flex-row overflow-y-auto min-h-0"> <div className="color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
<div className="flex-grow flex flex-col py-6 items-center"> <div className="flex-grow flex flex-col py-6 items-center">
<div className="w-125 max-w-125 flex flex-col"> <div className="w-125 max-w-125 flex flex-col">
{children != undefined && Array.isArray(children) {children != undefined && Array.isArray(children)

View File

@@ -3,6 +3,7 @@ import { AppState } from '@/ui_models/app_state';
import { FunctionComponent } from 'preact'; import { FunctionComponent } from 'preact';
import { PreferencesPane } from '../components'; import { PreferencesPane } from '../components';
import { ErrorReporting } from './general-segments'; import { ErrorReporting } from './general-segments';
import { Tools } from './general-segments/Tools';
interface GeneralProps { interface GeneralProps {
appState: AppState; appState: AppState;
@@ -11,6 +12,7 @@ interface GeneralProps {
export const General: FunctionComponent<GeneralProps> = (props) => ( export const General: FunctionComponent<GeneralProps> = (props) => (
<PreferencesPane> <PreferencesPane>
<Tools application={props.application} />
<ErrorReporting appState={props.appState} /> <ErrorReporting appState={props.appState} />
</PreferencesPane> </PreferencesPane>
); );

View File

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

@@ -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>
);
}
);

View File

@@ -1 +1,2 @@
export * from './ErrorReporting'; export * from './ErrorReporting';
export * from './Tools';

View File

@@ -1,4 +1,5 @@
export * from './HelpFeedback'; export * from './HelpFeedback';
export * from './Security'; export * from './Security';
export * from './AccountPreferences'; export * from './AccountPreferences';
export * from './Listed';
export * from './General'; 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" />}
</>
);
};

View File

@@ -15,7 +15,7 @@ const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(({
const archived = formatCount(count.archived, 'archived notes'); const archived = formatCount(count.archived, 'archived notes');
const deleted = formatCount(count.deleted, 'trashed 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 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 tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />;
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />; const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />;

View File

@@ -16,9 +16,8 @@ const DisclosureIconButton: FunctionComponent<{
<DisclosureButton <DisclosureButton
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${ className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${className ?? ''
className ?? '' }`}
}`}
> >
<Icon type={icon} /> <Icon type={icon} />
</DisclosureButton> </DisclosureButton>
@@ -58,7 +57,7 @@ export const AuthAppInfoTooltip: FunctionComponent = () => {
/> />
<DisclosurePanel> <DisclosurePanel>
<div <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`} py-1.5 px-2 absolute w-103 -top-10 -left-51`}
> >
Some apps, like Google Authenticator, do not back up and restore Some apps, like Google Authenticator, do not back up and restore

View File

@@ -3,5 +3,5 @@ import { FunctionComponent } from 'preact';
export const Bullet: FunctionComponent<{ className?: string }> = ({ export const Bullet: FunctionComponent<{ className?: string }> = ({
className = '', 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`} />
); );

View File

@@ -59,42 +59,6 @@
.sn-component(ng-if='self.note') .sn-component(ng-if='self.note')
#editor-menu-bar.sk-app-bar.no-edges #editor-menu-bar.sk-app-bar.no-edges
.left .left
.sk-app-bar-item(
click-outside=`self.setMenuState('showOptionsMenu', false)`,
is-open='self.state.showOptionsMenu',
ng-class="{'selected' : self.state.showOptionsMenu}",
ng-click="self.toggleMenu('showOptionsMenu')"
)
.sk-label Options
.sk-menu-panel.dropdown-menu(ng-if='self.state.showOptionsMenu')
.sk-menu-panel-section
.sk-menu-panel-header
.sk-menu-panel-header-title Global Display
menu-row(
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeyMonospace)"
circle="self.state.monospaceFont ? 'success' : 'neutral'",
desc="'Toggles the font style for the default editor'",
disabled='self.state.editorComponent',
label="'Monospace Font'",
subtitle="self.state.editorComponent ? 'Not available with editor extensions' : null"
)
menu-row(
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeySpellcheck)"
circle="self.state.spellcheck ? 'success' : 'neutral'",
desc="'Toggles spellcheck for the default editor'",
disabled='self.state.editorComponent',
label="'Spellcheck'",
subtitle=`
self.state.editorComponent
? 'Not available with editor extensions'
: (self.state.isDesktop ? 'May degrade editor performance' : null)
`)
menu-row(
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeyMarginResizers)"
circle="self.state.marginResizersEnabled ? 'success' : 'neutral'",
desc="'Allows for editor left and right margins to be resized'",
label="'Margin Resizers'"
)
.sk-app-bar-item( .sk-app-bar-item(
click-outside=`self.setMenuState('showEditorMenu', false)` click-outside=`self.setMenuState('showEditorMenu', false)`
is-open='self.state.showEditorMenu', is-open='self.state.showEditorMenu',

View File

@@ -1,6 +1,4 @@
import { import { STRING_SAVING_WHILE_DOCUMENT_HIDDEN } from './../../strings';
STRING_SAVING_WHILE_DOCUMENT_HIDDEN,
} from './../../strings';
import { Editor } from '@/ui_models/editor'; import { Editor } from '@/ui_models/editor';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { PanelPuppet, WebDirective } from '@/types'; import { PanelPuppet, WebDirective } from '@/types';
@@ -61,7 +59,6 @@ type EditorState = {
isDesktop?: boolean; isDesktop?: boolean;
syncTakingTooLong: boolean; syncTakingTooLong: boolean;
showActionsMenu: boolean; showActionsMenu: boolean;
showOptionsMenu: boolean;
showEditorMenu: boolean; showEditorMenu: boolean;
showHistoryMenu: boolean; showHistoryMenu: boolean;
spellcheck: boolean; spellcheck: boolean;
@@ -202,7 +199,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
}); });
this.autorun(() => { this.autorun(() => {
this.setState({ this.setState({
showProtectedWarning: this.appState.notes.showProtectedWarning showProtectedWarning: this.appState.notes.showProtectedWarning,
}); });
}); });
} }
@@ -216,7 +213,6 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
spellcheck: true, spellcheck: true,
syncTakingTooLong: false, syncTakingTooLong: false,
showActionsMenu: false, showActionsMenu: false,
showOptionsMenu: false,
showEditorMenu: false, showEditorMenu: false,
showHistoryMenu: false, showHistoryMenu: false,
noteStatus: undefined, noteStatus: undefined,
@@ -272,11 +268,11 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
async handleEditorNoteChange() { async handleEditorNoteChange() {
this.cancelPendingSetStatus(); this.cancelPendingSetStatus();
const note = this.editor.note; const note = this.editor.note;
const showProtectedWarning = note.protected && !this.application.hasProtectionSources(); const showProtectedWarning =
note.protected && !this.application.hasProtectionSources();
this.setShowProtectedWarning(showProtectedWarning); this.setShowProtectedWarning(showProtectedWarning);
await this.setState({ await this.setState({
showActionsMenu: false, showActionsMenu: false,
showOptionsMenu: false,
showEditorMenu: false, showEditorMenu: false,
showHistoryMenu: false, showHistoryMenu: false,
noteStatus: undefined, noteStatus: undefined,
@@ -364,12 +360,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
closeAllMenus(exclude?: string) { closeAllMenus(exclude?: string) {
const allMenus = [ const allMenus = ['showEditorMenu', 'showActionsMenu', 'showHistoryMenu'];
'showOptionsMenu',
'showEditorMenu',
'showActionsMenu',
'showHistoryMenu',
];
const menuState: any = {}; const menuState: any = {};
for (const candidate of allMenus) { for (const candidate of allMenus) {
if (candidate !== exclude) { if (candidate !== exclude) {
@@ -591,7 +582,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
clickedTextArea() { clickedTextArea() {
this.setMenuState('showOptionsMenu', false); this.closeAllMenus();
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -607,12 +598,6 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.lastEditorFocusEventSource = undefined; this.lastEditorFocusEventSource = undefined;
} }
selectedMenuItem(hide: boolean) {
if (hide) {
this.setMenuState('showOptionsMenu', false);
}
}
setShowProtectedWarning(show: boolean) { setShowProtectedWarning(show: boolean) {
this.appState.notes.setShowProtectedWarning(show); this.appState.notes.setShowProtectedWarning(show);
} }
@@ -757,13 +742,10 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
/** @components */ /** @components */
registerComponentHandler() { registerComponentHandler() {
this.unregisterComponent = this.application.componentManager!.registerHandler( this.unregisterComponent =
{ this.application.componentManager!.registerHandler({
identifier: 'editor', identifier: 'editor',
areas: [ areas: [ComponentArea.EditorStack, ComponentArea.Editor],
ComponentArea.EditorStack,
ComponentArea.Editor,
],
contextRequestHandler: (componentUuid) => { contextRequestHandler: (componentUuid) => {
const currentEditor = this.state.editorComponent; const currentEditor = this.state.editorComponent;
if ( if (
@@ -778,8 +760,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.closeAllMenus(); this.closeAllMenus();
} }
}, },
} });
);
} }
async reloadStackComponents() { async reloadStackComponents() {
@@ -809,9 +790,8 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
async toggleStackComponentForCurrentItem(component: SNComponent) { async toggleStackComponentForCurrentItem(component: SNComponent) {
const hidden = this.application.componentManager!.isComponentHidden( const hidden =
component this.application.componentManager!.isComponentHidden(component);
);
if (hidden || !component.active) { if (hidden || !component.active) {
this.application.componentManager!.setComponentHidden(component, false); this.application.componentManager!.setComponentHidden(component, false);
await this.associateComponentWithCurrentNote(component); await this.associateComponentWithCurrentNote(component);
@@ -844,16 +824,14 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
registerKeyboardShortcuts() { registerKeyboardShortcuts() {
this.removeTrashKeyObserver = this.application this.removeTrashKeyObserver = this.application.io.addKeyObserver({
.io key: KeyboardKey.Backspace,
.addKeyObserver({ notTags: ['INPUT', 'TEXTAREA'],
key: KeyboardKey.Backspace, modifiers: [KeyboardModifier.Meta],
notTags: ['INPUT', 'TEXTAREA'], onKeyDown: () => {
modifiers: [KeyboardModifier.Meta], this.deleteNote(false);
onKeyDown: () => { },
this.deleteNote(false); });
},
});
} }
setScrollPosition() { setScrollPosition() {
@@ -883,39 +861,37 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
const editor = document.getElementById( const editor = document.getElementById(
ElementIds.NoteTextEditor ElementIds.NoteTextEditor
)! as HTMLInputElement; )! as HTMLInputElement;
this.removeTabObserver = this.application this.removeTabObserver = this.application.io.addKeyObserver({
.io element: editor,
.addKeyObserver({ key: KeyboardKey.Tab,
element: editor, onKeyDown: (event) => {
key: KeyboardKey.Tab, if (document.hidden || this.note.locked || event.shiftKey) {
onKeyDown: (event) => { return;
if (document.hidden || this.note.locked || event.shiftKey) { }
return; event.preventDefault();
} /** Using document.execCommand gives us undo support */
event.preventDefault(); const insertSuccessful = document.execCommand(
/** Using document.execCommand gives us undo support */ 'insertText',
const insertSuccessful = document.execCommand( false,
'insertText', '\t'
false, );
'\t' if (!insertSuccessful) {
); /** document.execCommand works great on Chrome/Safari but not Firefox */
if (!insertSuccessful) { const start = editor.selectionStart!;
/** document.execCommand works great on Chrome/Safari but not Firefox */ const end = editor.selectionEnd!;
const start = editor.selectionStart!; const spaces = ' ';
const end = editor.selectionEnd!; /** Insert 4 spaces */
const spaces = ' '; editor.value =
/** Insert 4 spaces */ editor.value.substring(0, start) +
editor.value = spaces +
editor.value.substring(0, start) + editor.value.substring(end);
spaces + /** Place cursor 4 spaces away from where the tab key was pressed */
editor.value.substring(end); editor.selectionStart = editor.selectionEnd = start + 4;
/** Place cursor 4 spaces away from where the tab key was pressed */ }
editor.selectionStart = editor.selectionEnd = start + 4; this.editorValues.text = editor.value;
} this.save(this.note, copyEditorValues(this.editorValues), true);
this.editorValues.text = editor.value; },
this.save(this.note, copyEditorValues(this.editorValues), true); });
},
});
editor.addEventListener('scroll', this.setScrollPosition); editor.addEventListener('scroll', this.setScrollPosition);
editor.addEventListener('input', this.resetScrollPosition); editor.addEventListener('input', this.resetScrollPosition);

View File

@@ -1,28 +1,37 @@
.sn-component .sn-component
#footer-bar.sk-app-bar.no-edges.no-bottom-edge #footer-bar.sk-app-bar.no-edges.no-bottom-edge
.left .left
.sk-app-bar-item( .sk-app-bar-item.ml-0(
click-outside='ctrl.clickOutsideAccountMenu()', click-outside='ctrl.clickOutsideAccountMenu()',
is-open='ctrl.showAccountMenu', is-open='ctrl.showAccountMenu',
ng-click='ctrl.accountMenuPressed()' ng-click='ctrl.accountMenuPressed()'
) )
.sk-app-bar-item-column .w-8.h-full.flex.items-center.justify-center.cursor-pointer.rounded-full(
.sk-circle.small( ng-class="ctrl.showAccountMenu ? 'bg-border' : '' "
ng-class="ctrl.hasError ? 'danger' : (ctrl.user ? 'info' : 'neutral')" )
.w-5.h-5(
ng-class="ctrl.hasError ? 'danger' : (ctrl.user ? 'info' : 'neutral')"
) )
.sk-app-bar-item-column icon(
.sk-label.title(ng-class='{red: ctrl.hasError}') Account type="account-circle"
class-name="hover:color-info w-5 h-5 max-h-5"
)
account-menu( account-menu(
ng-click='$event.stopPropagation()', ng-click='$event.stopPropagation()',
app-state='ctrl.appState' app-state='ctrl.appState'
application='ctrl.application' application='ctrl.application'
ng-if='ctrl.showAccountMenu', ng-if='ctrl.showAccountMenu',
) )
.sk-app-bar-item( .sk-app-bar-item.ml-0-important(
ng-click='ctrl.clickPreferences()' ng-click='ctrl.clickPreferences()'
ng-if='ctrl.appState.enableUnfinishedFeatures' ng-if='ctrl.appState.enableUnfinishedFeatures'
) )
.sk-label.title Preferences .w-8.h-full.flex.items-center.justify-center.cursor-pointer
.h-5
icon(
type="tune"
class-name="rounded hover:color-info"
)
.sk-app-bar-item .sk-app-bar-item
a.no-decoration.sk-label.title( a.no-decoration.sk-label.title(
href='https://standardnotes.com/help', href='https://standardnotes.com/help',

View File

@@ -34,17 +34,6 @@
border-bottom: 2px solid var(--sn-stylekit-info-color); border-bottom: 2px solid var(--sn-stylekit-info-color);
} }
} }
svg {
width: 12px;
height: 12px;
fill: var(--sn-stylekit-secondary-foreground-color);
&:hover {
fill: var(--sn-stylekit-info-color) !important;
color: var(--sn-stylekit-info-color) !important;
}
}
} }
#account-switcher-icon { #account-switcher-icon {

View File

@@ -115,7 +115,7 @@ p {
background-color: var(--sn-stylekit-background-color); background-color: var(--sn-stylekit-background-color);
} }
$footer-height: 32px; $footer-height: 2rem;
#resizer-overlay { #resizer-overlay {
position: absolute; position: absolute;

View File

@@ -154,6 +154,13 @@
@extend .font-bold; @extend .font-bold;
} }
.ml-0-important {
margin-left: 0rem !important;
}
.ml-3 {
margin-left: 0.75rem;
}
.ml-4 { .ml-4 {
margin-left: 1rem; margin-left: 1rem;
} }
@@ -222,10 +229,22 @@
min-height: 1.5rem; min-height: 1.5rem;
} }
.max-h-5 {
max-height: 1.25rem;
}
.border-danger { .border-danger {
border-color: var(--sn-stylekit-danger-color); border-color: var(--sn-stylekit-danger-color);
} }
.bg-inverted-default {
background-color: var(--sn-stylekit-contrast-foreground-color);
}
.color-inverted-default {
color: var(--sn-stylekit-background-color);
}
.pt-1 { .pt-1 {
padding-top: 0.25rem; padding-top: 0.25rem;
} }
@@ -256,3 +275,7 @@
padding-top: 2.25rem; padding-top: 2.25rem;
padding-bottom: 2.25rem; padding-bottom: 2.25rem;
} }
.select-none {
user-select: none;
}

View File

@@ -72,7 +72,7 @@
"@reach/dialog": "^0.13.0", "@reach/dialog": "^0.13.0",
"@standardnotes/sncrypto-web": "1.5.2", "@standardnotes/sncrypto-web": "1.5.2",
"@standardnotes/features": "1.6.1", "@standardnotes/features": "1.6.1",
"@standardnotes/snjs": "2.14.6", "@standardnotes/snjs": "2.14.8",
"mobx": "^6.3.2", "mobx": "^6.3.2",
"mobx-react-lite": "^3.2.0", "mobx-react-lite": "^3.2.0",
"preact": "^10.5.12", "preact": "^10.5.12",