feat: preferences help & feedback (#596)
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0001 12.9167C9.22656 12.9167 8.4847 12.6095 7.93772 12.0625C7.39073 11.5155 7.08344 10.7736 7.08344 10.0001C7.08344 9.22653 7.39073 8.48467 7.93772 7.93769C8.4847 7.39071 9.22656 7.08342 10.0001 7.08342C10.7737 7.08342 11.5155 7.39071 12.0625 7.93769C12.6095 8.48467 12.9168 9.22653 12.9168 10.0001C12.9168 10.7736 12.6095 11.5155 12.0625 12.0625C11.5155 12.6095 10.7737 12.9167 10.0001 12.9167ZM16.1918 10.8084C16.2251 10.5417 16.2501 10.2751 16.2501 10.0001C16.2501 9.72508 16.2251 9.45008 16.1918 9.16675L17.9501 7.80841C18.1084 7.68341 18.1501 7.45841 18.0501 7.27508L16.3834 4.39175C16.2834 4.20841 16.0584 4.13341 15.8751 4.20842L13.8001 5.04175C13.3668 4.71675 12.9168 4.43341 12.3918 4.22508L12.0834 2.01675C12.0501 1.81675 11.8751 1.66675 11.6668 1.66675H8.33344C8.12511 1.66675 7.95011 1.81675 7.91678 2.01675L7.60844 4.22508C7.08344 4.43341 6.63344 4.71675 6.20011 5.04175L4.12511 4.20842C3.94178 4.13341 3.71678 4.20841 3.61678 4.39175L1.95011 7.27508C1.84178 7.45841 1.89178 7.68341 2.05011 7.80841L3.80844 9.16675C3.77511 9.45008 3.75011 9.72508 3.75011 10.0001C3.75011 10.2751 3.77511 10.5417 3.80844 10.8084L2.05011 12.1917C1.89178 12.3167 1.84178 12.5417 1.95011 12.7251L3.61678 15.6084C3.71678 15.7917 3.94178 15.8584 4.12511 15.7917L6.20011 14.9501C6.63344 15.2834 7.08344 15.5667 7.60844 15.7751L7.91678 17.9834C7.95011 18.1834 8.12511 18.3334 8.33344 18.3334H11.6668C11.8751 18.3334 12.0501 18.1834 12.0834 17.9834L12.3918 15.7751C12.9168 15.5584 13.3668 15.2834 13.8001 14.9501L15.8751 15.7917C16.0584 15.8584 16.2834 15.7917 16.3834 15.6084L18.0501 12.7251C18.1501 12.5417 18.1084 12.3167 17.9501 12.1917L16.1918 10.8084Z" fill="#72767E"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
3
app/assets/icons/ic-settings.svg
Normal file
3
app/assets/icons/ic-settings.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0001 6.66675C10.8842 6.66675 11.732 7.01794 12.3571 7.64306C12.9823 8.26818 13.3334 9.11603 13.3334 10.0001C13.3334 10.8841 12.9823 11.732 12.3571 12.3571C11.732 12.9822 10.8842 13.3334 10.0001 13.3334C9.11606 13.3334 8.26821 12.9822 7.64309 12.3571C7.01797 11.732 6.66678 10.8841 6.66678 10.0001C6.66678 9.11603 7.01797 8.26818 7.64309 7.64306C8.26821 7.01794 9.11606 6.66675 10.0001 6.66675ZM10.0001 8.33342C9.55808 8.33342 9.13416 8.50901 8.8216 8.82157C8.50904 9.13413 8.33344 9.55805 8.33344 10.0001C8.33344 10.4421 8.50904 10.866 8.8216 11.1786C9.13416 11.4912 9.55808 11.6667 10.0001 11.6667C10.4421 11.6667 10.8661 11.4912 11.1786 11.1786C11.4912 10.866 11.6668 10.4421 11.6668 10.0001C11.6668 9.55805 11.4912 9.13413 11.1786 8.82157C10.8661 8.50901 10.4421 8.33342 10.0001 8.33342ZM8.33344 18.3334C8.12511 18.3334 7.95011 18.1834 7.91678 17.9834L7.60844 15.7751C7.08344 15.5667 6.63344 15.2834 6.20011 14.9501L4.12511 15.7917C3.94178 15.8584 3.71678 15.7917 3.61678 15.6084L1.95011 12.7251C1.84178 12.5417 1.89178 12.3167 2.05011 12.1917L3.80844 10.8084L3.75011 10.0001L3.80844 9.16675L2.05011 7.80841C1.89178 7.68341 1.84178 7.45841 1.95011 7.27508L3.61678 4.39175C3.71678 4.20841 3.94178 4.13341 4.12511 4.20842L6.20011 5.04175C6.63344 4.71675 7.08344 4.43341 7.60844 4.22508L7.91678 2.01675C7.95011 1.81675 8.12511 1.66675 8.33344 1.66675H11.6668C11.8751 1.66675 12.0501 1.81675 12.0834 2.01675L12.3918 4.22508C12.9168 4.43341 13.3668 4.71675 13.8001 5.04175L15.8751 4.20842C16.0584 4.13341 16.2834 4.20841 16.3834 4.39175L18.0501 7.27508C18.1584 7.45841 18.1084 7.68341 17.9501 7.80841L16.1918 9.16675L16.2501 10.0001L16.1918 10.8334L17.9501 12.1917C18.1084 12.3167 18.1584 12.5417 18.0501 12.7251L16.3834 15.6084C16.2834 15.7917 16.0584 15.8667 15.8751 15.7917L13.8001 14.9584C13.3668 15.2834 12.9168 15.5667 12.3918 15.7751L12.0834 17.9834C12.0501 18.1834 11.8751 18.3334 11.6668 18.3334H8.33344ZM9.37511 3.33341L9.06678 5.50841C8.06678 5.71675 7.18344 6.25008 6.54178 6.99175L4.53344 6.12508L3.90844 7.20841L5.66678 8.50008C5.33344 9.47508 5.33344 10.5334 5.66678 11.5001L3.90011 12.8001L4.52511 13.8834L6.55011 13.0167C7.19178 13.7501 8.06678 14.2834 9.05844 14.4834L9.36678 16.6667H10.6334L10.9418 14.4917C11.9334 14.2834 12.8084 13.7501 13.4501 13.0167L15.4751 13.8834L16.1001 12.8001L14.3334 11.5084C14.6668 10.5334 14.6668 9.47508 14.3334 8.50008L16.0918 7.20841L15.4668 6.12508L13.4584 6.99175C12.8168 6.25008 11.9334 5.71675 10.9334 5.51675L10.6251 3.33341H9.37511Z" fill="#72767E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -19,7 +19,7 @@ import HelpIcon from '../../icons/ic-help.svg';
|
||||
import KeyboardIcon from '../../icons/ic-keyboard.svg';
|
||||
import ListedIcon from '../../icons/ic-listed.svg';
|
||||
import SecurityIcon from '../../icons/ic-security.svg';
|
||||
import SettingsFilledIcon from '../../icons/ic-settings-filled.svg';
|
||||
import SettingsIcon from '../../icons/ic-settings.svg';
|
||||
import StarIcon from '../../icons/ic-star.svg';
|
||||
import ThemesIcon from '../../icons/ic-themes.svg';
|
||||
import UserIcon from '../../icons/ic-user.svg';
|
||||
@@ -47,7 +47,7 @@ const ICONS = {
|
||||
keyboard: KeyboardIcon,
|
||||
listed: ListedIcon,
|
||||
security: SecurityIcon,
|
||||
'settings-filled': SettingsFilledIcon,
|
||||
settings: SettingsIcon,
|
||||
star: StarIcon,
|
||||
themes: ThemesIcon,
|
||||
user: UserIcon,
|
||||
|
||||
28
app/assets/javascripts/components/preferences/content.tsx
Normal file
28
app/assets/javascripts/components/preferences/content.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { FunctionalComponent } from 'preact';
|
||||
|
||||
export const Title: FunctionalComponent = ({ children }) => (
|
||||
<h2 className="text-base m-0 mb-3">{children}</h2>
|
||||
);
|
||||
|
||||
export const Subtitle: FunctionalComponent = ({ children }) => (
|
||||
<h4 className="font-medium text-sm m-0 mb-1">{children}</h4>
|
||||
);
|
||||
|
||||
export const Text: FunctionalComponent = ({ children }) => (
|
||||
<p className="text-xs">{children}</p>
|
||||
);
|
||||
|
||||
export const Button: FunctionalComponent<{ label: string; link: string }> = ({
|
||||
label,
|
||||
link,
|
||||
}) => (
|
||||
<a
|
||||
target="_blank"
|
||||
className="block bg-default color-text rounded border-solid border-1
|
||||
border-gray-300 px-4 py-2 font-bold text-sm fit-content mt-3
|
||||
focus:bg-contrast hover:bg-contrast "
|
||||
href={link}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
@@ -0,0 +1,92 @@
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { PreferencesGroup, PreferencesPane, PreferencesSegment } from './pane';
|
||||
import { Title, Subtitle, Text, Button } from './content';
|
||||
|
||||
export const HelpAndFeedback: FunctionalComponent = () => (
|
||||
<PreferencesPane>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Frequently asked questions</Title>
|
||||
<Subtitle>Who can read my private notes?</Subtitle>
|
||||
<Text>
|
||||
Quite simply: no one but you. Not us, not your ISP, not a hacker, and
|
||||
not a government agency. As long as you keep your password safe, and
|
||||
your password is reasonably strong, then you are the only person in
|
||||
the world with the ability to decrypt your notes. For more on how we
|
||||
handle your privacy and security, check out our easy to read{' '}
|
||||
<a target="_blank" href="https://standardnotes.com/privacy">
|
||||
Privacy Manifesto.
|
||||
</a>
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
||||
<Text>
|
||||
Because of our encrypted architecture, Standard Notes does not
|
||||
currently provide a real-time collaboration solution. Multiple users
|
||||
can share the same account however, but editing at the same time may
|
||||
result in sync conflicts, which may result in the duplication of
|
||||
notes.
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
||||
<Text>
|
||||
Standard Notes can be used totally offline without an account, and
|
||||
without an internet connection. You can find{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
|
||||
>
|
||||
more details here.
|
||||
</a>
|
||||
</Text>
|
||||
</PreferencesSegment>
|
||||
<PreferencesSegment>
|
||||
<Subtitle>Can’t find your question here?</Subtitle>
|
||||
<Button label="Open FAQ" link="https://standardnotes.com/help" />
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Community forum</Title>
|
||||
<Text>
|
||||
If you have an issue, found a bug or want to suggest a feature, you
|
||||
can browse or post to the forum. It’s recommended for non-account
|
||||
related issues. Please read our{' '}
|
||||
<a target="_blank" href="https://standardnotes.com/longevity/">
|
||||
Longevity statement
|
||||
</a>{' '}
|
||||
before advocating for a feature request.
|
||||
</Text>
|
||||
<Button
|
||||
label="Go to the forum"
|
||||
link="https://forum.standardnotes.org/"
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Slack group</Title>
|
||||
<Text>
|
||||
Want to meet other passionate note-takers and privacy enthusiasts?
|
||||
Want to share your feedback with us? Join the Standard Notes Slack
|
||||
group for discussions on security, themes, editors and more.
|
||||
</Text>
|
||||
<Button
|
||||
link="https://standardnotes.com/slack"
|
||||
label="Join our Slack group"
|
||||
/>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Account related issue?</Title>
|
||||
<Text>
|
||||
Send an email to help@standardnotes.org and we’ll sort it out.
|
||||
</Text>
|
||||
<Button link="mailto: help@standardnotes.org" label="Email us" />
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
</PreferencesPane>
|
||||
);
|
||||
23
app/assets/javascripts/components/preferences/menu.tsx
Normal file
23
app/assets/javascripts/components/preferences/menu.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { PreferencesMenuItem } from '../PreferencesMenuItem';
|
||||
import { Preferences } from './preferences';
|
||||
|
||||
interface PreferencesMenuProps {
|
||||
preferences: Preferences;
|
||||
}
|
||||
|
||||
export const PreferencesMenu: FunctionComponent<PreferencesMenuProps> =
|
||||
observer(({ preferences }) => (
|
||||
<div className="min-w-55 overflow-y-auto flex flex-col px-3 py-6">
|
||||
{preferences.items.map((pref) => (
|
||||
<PreferencesMenuItem
|
||||
key={pref.id}
|
||||
iconType={pref.icon}
|
||||
label={pref.label}
|
||||
selected={pref.selected}
|
||||
onClick={() => preferences.selectItem(pref.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
@@ -1,33 +1,33 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { PreferencesMenuItem } from '../PreferencesMenuItem';
|
||||
import { MockState } from './mock-state';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
|
||||
interface PreferencesMenuProps {
|
||||
store: MockState;
|
||||
}
|
||||
const HorizontalLine: FunctionalComponent<{ index: number; length: number }> =
|
||||
({ index, length }) =>
|
||||
index < length - 1 ? (
|
||||
<hr className="h-1px w-full bg-border no-border" />
|
||||
) : null;
|
||||
|
||||
const PreferencesMenu: FunctionComponent<PreferencesMenuProps> = observer(
|
||||
({ store }) => (
|
||||
<div className="h-full w-auto flex flex-col px-3 py-6">
|
||||
{store.items.map((pref) => (
|
||||
<PreferencesMenuItem
|
||||
key={pref.id}
|
||||
iconType={pref.icon}
|
||||
label={pref.label}
|
||||
selected={pref.selected}
|
||||
onClick={() => store.select(pref.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
export const PreferencesSegment: FunctionalComponent = ({ children }) => (
|
||||
<div>{children}</div>
|
||||
);
|
||||
|
||||
export const PreferencesPane: FunctionComponent = () => {
|
||||
const store = new MockState();
|
||||
return (
|
||||
<div className="h-full w-full flex flex-row">
|
||||
<PreferencesMenu store={store}></PreferencesMenu>
|
||||
export const PreferencesGroup: FunctionalComponent = ({ children }) => (
|
||||
<div className="bg-default border-1 border-solid rounded border-gray-300 px-6 py-6 flex flex-col gap-2">
|
||||
{!Array.isArray(children)
|
||||
? children
|
||||
: children.map((c, i, arr) => (
|
||||
<>
|
||||
{c}
|
||||
<HorizontalLine index={i} length={arr.length} />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const PreferencesPane: FunctionalComponent = ({ children }) => (
|
||||
<div className="preferences-pane flex-grow flex flex-row overflow-y-auto min-h-0">
|
||||
<div className="flex-grow flex flex-col py-6 items-center">
|
||||
<div className="max-w-124 flex flex-col gap-3">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<div className="flex-basis-55 flex-shrink-max" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ interface PreferenceListItem extends PreferenceItem {
|
||||
}
|
||||
|
||||
const predefinedItems: PreferenceItem[] = [
|
||||
{ label: 'General', icon: 'settings-filled' },
|
||||
{ label: 'General', icon: 'settings' },
|
||||
{ label: 'Account', icon: 'user' },
|
||||
{ label: 'Appearance', icon: 'themes' },
|
||||
{ label: 'Security', icon: 'security' },
|
||||
@@ -22,22 +22,23 @@ const predefinedItems: PreferenceItem[] = [
|
||||
{ label: 'Help & feedback', icon: 'help' },
|
||||
];
|
||||
|
||||
export class MockState {
|
||||
export class Preferences {
|
||||
private readonly _items: PreferenceListItem[];
|
||||
private _selectedId = 0;
|
||||
|
||||
constructor(items: PreferenceItem[] = predefinedItems) {
|
||||
makeObservable<MockState, '_selectedId'>(this, {
|
||||
makeObservable<Preferences, '_selectedId'>(this, {
|
||||
_selectedId: observable,
|
||||
selectedItem: computed,
|
||||
items: computed,
|
||||
select: action,
|
||||
selectItem: action,
|
||||
});
|
||||
|
||||
this._items = items.map((p, idx) => ({ ...p, id: idx }));
|
||||
this._selectedId = this._items[0].id;
|
||||
}
|
||||
|
||||
select(id: number) {
|
||||
selectItem(id: number) {
|
||||
this._selectedId = id;
|
||||
}
|
||||
|
||||
@@ -47,4 +48,8 @@ export class MockState {
|
||||
selected: p.id === this._selectedId,
|
||||
}));
|
||||
}
|
||||
|
||||
get selectedItem(): PreferenceListItem {
|
||||
return this._items.find((item) => item.id === this._selectedId)!;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,45 @@
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { TitleBar, Title } from '@/components/TitleBar';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { PreferencesPane } from './pane';
|
||||
import { Preferences } from './preferences';
|
||||
import { PreferencesMenu } from './menu';
|
||||
import { HelpAndFeedback } from './help-feedback';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
interface PreferencesViewProps {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const PreferencesView: FunctionComponent<PreferencesViewProps> = ({
|
||||
close,
|
||||
}) => (
|
||||
<div className="sn-full-screen flex flex-col bg-contrast z-index-preferences">
|
||||
<TitleBar className="items-center justify-between">
|
||||
{/* div is added so flex justify-between can center the title */}
|
||||
<div className="h-8 w-8" />
|
||||
<Title className="text-lg">Your preferences for Standard Notes</Title>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
type="normal"
|
||||
iconType="close"
|
||||
/>
|
||||
</TitleBar>
|
||||
<PreferencesPane />
|
||||
export const PreferencesCanvas: FunctionComponent<{
|
||||
preferences: Preferences;
|
||||
}> = observer(({ preferences: prefs }) => (
|
||||
<div className="flex flex-row flex-grow min-h-0 justify-between">
|
||||
<PreferencesMenu preferences={prefs}></PreferencesMenu>
|
||||
{/* Temporary selector until a full solution is implemented */}
|
||||
{prefs.selectedItem.label === 'Help & feedback' ? (
|
||||
<HelpAndFeedback />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
));
|
||||
|
||||
export const PreferencesView: FunctionComponent<PreferencesViewProps> =
|
||||
observer(({ close }) => {
|
||||
const prefs = new Preferences();
|
||||
return (
|
||||
<div className="sn-full-screen flex flex-col bg-contrast z-index-preferences">
|
||||
<TitleBar className="items-center justify-between">
|
||||
{/* div is added so flex justify-between can center the title */}
|
||||
<div className="h-8 w-8" />
|
||||
<Title className="text-lg">Your preferences for Standard Notes</Title>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
type="normal"
|
||||
iconType="close"
|
||||
/>
|
||||
</TitleBar>
|
||||
<PreferencesCanvas preferences={prefs} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
@extend .border-gray-300;
|
||||
@extend .border-solid;
|
||||
@extend .border-1;
|
||||
@extend .bg-default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pug-loader": "^2.4.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"serve-static": "^1.14.1",
|
||||
"sn-stylekit": "5.2.3",
|
||||
"sn-stylekit": "5.2.5",
|
||||
"ts-loader": "^8.0.17",
|
||||
"typescript": "4.2.3",
|
||||
"typescript-eslint": "0.0.1-alpha.0",
|
||||
|
||||
@@ -7908,10 +7908,10 @@ slice-ansi@^4.0.0:
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
sn-stylekit@5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.2.3.tgz#24246471c03cde5129bda51a08fabef4d3c4880c"
|
||||
integrity sha512-hzziH89IY2UjmGh8OYgapb+/QVD6P6NNjnoyzSyveOh671MM9Z4IaPLZTJckgxJVjV0q7G495Pxfta5r4CSRDQ==
|
||||
sn-stylekit@5.2.5:
|
||||
version "5.2.5"
|
||||
resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.2.5.tgz#85a28da395fedbaae9f7a91c48648042cdcc8052"
|
||||
integrity sha512-8J+8UtRvukyJOBp79RcD4IZrvJJbjYY6EdN4N125K0xW84nDjgURuPuCjwm4lnp6vcXODU6r5d3JMDJoXYq8wA==
|
||||
dependencies:
|
||||
"@reach/listbox" "^0.15.0"
|
||||
"@reach/menu-button" "^0.15.1"
|
||||
|
||||
Reference in New Issue
Block a user