feat(web): tailwind css (#1147)
This commit is contained in:
BIN
.yarn/cache/postcss-loader-npm-7.0.0-e0a0c61fcd-b8e51e9989.zip
vendored
Normal file
BIN
.yarn/cache/postcss-loader-npm-7.0.0-e0a0c61fcd-b8e51e9989.zip
vendored
Normal file
Binary file not shown.
@@ -37,7 +37,7 @@ const GroupSummary: React.FC<GroupSummaryProps> = ({ groups }) => {
|
||||
return (
|
||||
<p data-testid="group-summary" key={`group-${group.name}`} className="mb-1">
|
||||
{truncateText(group.name, MAX_GROUP_DESCRIPTION_LENGTH)}
|
||||
<span className="px-2 neutral">
|
||||
<span className="px-2 text-neutral">
|
||||
{totalCompletedTasks}/{totalTasks}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -69,7 +69,7 @@ panel-resizer {
|
||||
/* Required for BrowserWindow titleBarStyle: 'hiddenInset' */
|
||||
.mac-desktop #navigation,
|
||||
.mac-desktop #navigation .section-title-bar,
|
||||
.mac-desktop #notes-title-bar,
|
||||
.mac-desktop #items-title-bar,
|
||||
.mac-desktop #editor-title-bar,
|
||||
.mac-desktop #lock-screen {
|
||||
-webkit-app-region: drag;
|
||||
|
||||
@@ -23,7 +23,7 @@ export class SKAlert {
|
||||
buttonsString() {
|
||||
const genButton = function (buttonDesc: AlertButton, index: number) {
|
||||
return `
|
||||
<button id='button-${index}' class='sn-button small ${buttonDesc.style}'>
|
||||
<button id='button-${index}' class='font-bold px-2.5 py-2 text-xs text-info-contrast bg-${buttonDesc.style}'>
|
||||
<div class='sk-label'>${buttonDesc.text}</div>
|
||||
</button>
|
||||
`
|
||||
|
||||
68
packages/styles/src/Styles/_colors.scss
Normal file
68
packages/styles/src/Styles/_colors.scss
Normal file
@@ -0,0 +1,68 @@
|
||||
:root {
|
||||
--sn-stylekit-neutral-color: #989898;
|
||||
--sn-stylekit-neutral-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-info-color: #086dd6;
|
||||
--sn-stylekit-info-color-darkened: #065cb5;
|
||||
--sn-stylekit-info-contrast-color: #ffffff;
|
||||
--sn-stylekit-info-backdrop-color: #2b6fcf0f;
|
||||
|
||||
--sn-stylekit-success-color: #007662;
|
||||
--sn-stylekit-success-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-warning-color: #ebad00;
|
||||
--sn-stylekit-warning-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-danger-color: #cc2128;
|
||||
--sn-stylekit-danger-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-shadow-color: #c8c8c8;
|
||||
--sn-stylekit-background-color: #ffffff;
|
||||
// For borders inside background-color
|
||||
--sn-stylekit-border-color: #dfe1e4;
|
||||
--sn-stylekit-foreground-color: #000000;
|
||||
// Colors for layers placed on top of non-prefixed background, border, and foreground
|
||||
--sn-stylekit-contrast-background-color: #f6f6f6;
|
||||
--sn-stylekit-contrast-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-contrast-border-color: #e3e3e3; // For borders inside contrast-background-color
|
||||
|
||||
// Alternative set of background and contrast options
|
||||
--sn-stylekit-secondary-background-color: #f6f6f6;
|
||||
--sn-stylekit-secondary-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-secondary-border-color: #e3e3e3;
|
||||
|
||||
--sn-stylekit-secondary-contrast-background-color: #e3e3e3;
|
||||
--sn-stylekit-secondary-contrast-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-secondary-contrast-border-color: #a2a2a2;
|
||||
|
||||
--sn-stylekit-editor-background-color: var(--sn-stylekit-background-color);
|
||||
--sn-stylekit-editor-foreground-color: var(--sn-stylekit-foreground-color);
|
||||
|
||||
--sn-stylekit-paragraph-text-color: #454545;
|
||||
|
||||
--sn-stylekit-input-placeholder-color: #a8a8a8;
|
||||
--sn-stylekit-input-border-color: #e3e3e3;
|
||||
|
||||
--sn-stylekit-scrollbar-thumb-color: #dfdfdf;
|
||||
--sn-stylekit-scrollbar-track-border-color: #e7e7e7;
|
||||
|
||||
--sn-stylekit-theme-type: light;
|
||||
--sn-stylekit-theme-name: sn-light;
|
||||
|
||||
--sn-stylekit-passive-color-0: #515357;
|
||||
--sn-stylekit-passive-color-1: #72767e;
|
||||
--sn-stylekit-passive-color-2: #bbbec4;
|
||||
--sn-stylekit-passive-color-3: #dfe1e4;
|
||||
--sn-stylekit-passive-color-4: #eeeff1;
|
||||
--sn-stylekit-passive-color-4-opacity-variant: #bbbec43d;
|
||||
--sn-stylekit-passive-color-5: #f4f5f7;
|
||||
--sn-stylekit-passive-color-6: #e5e5e5;
|
||||
--sn-stylekit-passive-color-super-light: #f9f9f9;
|
||||
|
||||
--sn-stylekit-accessory-tint-color-1: #086dd6;
|
||||
--sn-stylekit-accessory-tint-color-2: #ea6595;
|
||||
--sn-stylekit-accessory-tint-color-3: #ebad00;
|
||||
--sn-stylekit-accessory-tint-color-4: #7049cf;
|
||||
--sn-stylekit-accessory-tint-color-5: #1aa772;
|
||||
--sn-stylekit-accessory-tint-color-6: #f28c52;
|
||||
}
|
||||
@@ -131,7 +131,8 @@
|
||||
}
|
||||
|
||||
.sk-panel-section-subtitle {
|
||||
@extend .sk-label;
|
||||
font-size: var(--sn-stylekit-font-size-p);
|
||||
font-weight: bold;
|
||||
font-size: var(--sn-stylekit-font-size-h5);
|
||||
margin-bottom: 2px;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import 'normalize';
|
||||
@import 'colors';
|
||||
|
||||
:root {
|
||||
--sn-stylekit-base-font-size: 0.8125rem;
|
||||
@@ -13,53 +14,6 @@
|
||||
--sn-stylekit-font-size-h2: 0.975rem;
|
||||
--sn-stylekit-font-size-h1: 1.05625rem;
|
||||
|
||||
--sn-stylekit-neutral-color: #989898;
|
||||
--sn-stylekit-neutral-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-info-color: #086DD6;
|
||||
--sn-stylekit-info-color-darkened: #065cb5;
|
||||
--sn-stylekit-info-contrast-color: #ffffff;
|
||||
--sn-stylekit-info-backdrop-color: #2b6fcf0f;
|
||||
|
||||
--sn-stylekit-success-color: #007662;
|
||||
--sn-stylekit-success-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-warning-color: #EBAD00;
|
||||
--sn-stylekit-warning-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-danger-color: #cc2128;
|
||||
--sn-stylekit-danger-contrast-color: #ffffff;
|
||||
|
||||
--sn-stylekit-shadow-color: #c8c8c8;
|
||||
--sn-stylekit-background-color: #ffffff;
|
||||
// For borders inside background-color
|
||||
--sn-stylekit-border-color: #dfe1e4;
|
||||
--sn-stylekit-foreground-color: #000000;
|
||||
// Colors for layers placed on top of non-prefixed background, border, and foreground
|
||||
--sn-stylekit-contrast-background-color: #f6f6f6;
|
||||
--sn-stylekit-contrast-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-contrast-border-color: #e3e3e3; // For borders inside contrast-background-color
|
||||
|
||||
// Alternative set of background and contrast options
|
||||
--sn-stylekit-secondary-background-color: #f6f6f6;
|
||||
--sn-stylekit-secondary-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-secondary-border-color: #e3e3e3;
|
||||
|
||||
--sn-stylekit-secondary-contrast-background-color: #e3e3e3;
|
||||
--sn-stylekit-secondary-contrast-foreground-color: #2e2e2e;
|
||||
--sn-stylekit-secondary-contrast-border-color: #a2a2a2;
|
||||
|
||||
--sn-stylekit-editor-background-color: var(--sn-stylekit-background-color);
|
||||
--sn-stylekit-editor-foreground-color: var(--sn-stylekit-foreground-color);
|
||||
|
||||
--sn-stylekit-paragraph-text-color: #454545;
|
||||
|
||||
--sn-stylekit-input-placeholder-color: #a8a8a8;
|
||||
--sn-stylekit-input-border-color: #e3e3e3;
|
||||
|
||||
--sn-stylekit-scrollbar-thumb-color: #dfdfdf;
|
||||
--sn-stylekit-scrollbar-track-border-color: #e7e7e7;
|
||||
|
||||
--sn-stylekit-menu-border: none;
|
||||
|
||||
--sn-stylekit-general-border-radius: 2px;
|
||||
@@ -70,26 +24,6 @@
|
||||
--sn-stylekit-sans-serif-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', var(--sn-stylekit-simplified-chinese-font), sans-serif;
|
||||
--sn-stylekit-editor-font-family: var(--sn-stylekit-sans-serif-font);
|
||||
|
||||
--sn-stylekit-theme-type: light;
|
||||
--sn-stylekit-theme-name: sn-light;
|
||||
|
||||
--sn-stylekit-passive-color-0: #515357;
|
||||
--sn-stylekit-passive-color-1: #72767e;
|
||||
--sn-stylekit-passive-color-2: #bbbec4;
|
||||
--sn-stylekit-passive-color-3: #dfe1e4;
|
||||
--sn-stylekit-passive-color-4: #eeeff1;
|
||||
--sn-stylekit-passive-color-4-opacity-variant: #bbbec43d;
|
||||
--sn-stylekit-passive-color-5: #f4f5f7;
|
||||
--sn-stylekit-passive-color-6: #e5e5e5;
|
||||
--sn-stylekit-passive-color-super-light: #f9f9f9;
|
||||
|
||||
--sn-stylekit-accessory-tint-color-1: #086dd6;
|
||||
--sn-stylekit-accessory-tint-color-2: #ea6595;
|
||||
--sn-stylekit-accessory-tint-color-3: #ebad00;
|
||||
--sn-stylekit-accessory-tint-color-4: #7049cf;
|
||||
--sn-stylekit-accessory-tint-color-5: #1aa772;
|
||||
--sn-stylekit-accessory-tint-color-6: #f28c52;
|
||||
}
|
||||
|
||||
.sn-component {
|
||||
|
||||
@@ -12,23 +12,23 @@ const prefersReducedMotion = () => {
|
||||
const colorForToastType = (type: ToastType) => {
|
||||
switch (type) {
|
||||
case ToastType.Success:
|
||||
return 'color-success'
|
||||
return 'text-success'
|
||||
case ToastType.Error:
|
||||
return 'color-danger'
|
||||
return 'text-danger'
|
||||
default:
|
||||
return 'color-info'
|
||||
return 'text-info'
|
||||
}
|
||||
}
|
||||
|
||||
const iconForToastType = (type: ToastType) => {
|
||||
switch (type) {
|
||||
case ToastType.Success:
|
||||
return <CheckCircleFilledIcon className={colorForToastType(type)} />
|
||||
return <CheckCircleFilledIcon className={`w-5 h-5 ${colorForToastType(type)}`} />
|
||||
case ToastType.Error:
|
||||
return <ClearCircleFilledIcon className={colorForToastType(type)} />
|
||||
return <ClearCircleFilledIcon className={`w-5 h-5 ${colorForToastType(type)}`} />
|
||||
case ToastType.Progress:
|
||||
case ToastType.Loading:
|
||||
return <div className="sk-spinner w-4 h-4 spinner-info" />
|
||||
return <div className="animate-spin border border-solid border-info border-r-transparent rounded-full w-4 h-4" />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export const Toast = forwardRef(({ toast, index }: Props, ref: ForwardedRef<HTML
|
||||
>
|
||||
<div className={`flex items-center w-full ${hasActions ? 'p-2 pl-3' : hasProgress ? 'px-3 py-2.5' : 'p-3'}`}>
|
||||
{icon ? <div className="flex flex-shrink-0 items-center justify-center sn-icon mr-2">{icon}</div> : null}
|
||||
<div className="text-sm">{toast.message}</div>
|
||||
<div className="text-sm text-text">{toast.message}</div>
|
||||
{hasActions && (
|
||||
<div className="ml-4">
|
||||
{toast.actions?.map((action, index) => (
|
||||
@@ -116,9 +116,9 @@ export const Toast = forwardRef(({ toast, index }: Props, ref: ForwardedRef<HTML
|
||||
)}
|
||||
</div>
|
||||
{hasProgress && (
|
||||
<div className="toast-progress-bar">
|
||||
<div className="rounded w-full bg-default overflow-hidden rounded-tl-none rounded-tr-none">
|
||||
<div
|
||||
className="toast-progress-bar__value"
|
||||
className="rounded h-2 bg-info rounded-tl-none transition-[width] duration-100"
|
||||
role="progressbar"
|
||||
style={{
|
||||
width: `${toast.progress}%`,
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ToastContainer: FunctionComponent = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-end fixed z-index-toast bottom-6 right-6">
|
||||
<div className="flex flex-col items-end fixed z-toast bottom-6 right-6">
|
||||
{toasts.map((toast, index) => (
|
||||
<ToastTimer toast={toast} index={index} key={toast.id} />
|
||||
))}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@types/react": "^17.0.42",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/wicg-file-system-access": "^2020.9.5",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"babel-loader": "^8.2.5",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"css-loader": "*",
|
||||
@@ -43,9 +44,12 @@
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"node-sass": "*",
|
||||
"npm-check-updates": "*",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-loader": "^7.0.0",
|
||||
"prettier": "*",
|
||||
"sass-loader": "*",
|
||||
"svg-jest": "^1.0.1",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "*",
|
||||
|
||||
6
packages/web/postcss.config.js
Normal file
6
packages/web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -58,8 +58,8 @@ const AccountMenu: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<div ref={ref} id="account-menu" className="sn-component">
|
||||
<div
|
||||
className={`sn-account-menu sn-dropdown ${
|
||||
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
|
||||
className={`z-footer-bar-item-panel bottom-full left-0 cursor-auto bg-default rounded shadow-main ${
|
||||
shouldAnimateCloseMenu ? 'slide-up-animation' : 'transition-transform duration-150 slide-down-animation'
|
||||
} min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto absolute`}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
|
||||
@@ -98,12 +98,12 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none font-bold"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full text-sm focus:bg-info-backdrop focus:shadow-none font-bold"
|
||||
onClick={toggleShowAdvanced}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
Advanced options
|
||||
<Icon type="chevron-down" className="color-passive-1 ml-1" />
|
||||
<Icon type="chevron-down" className="text-passive-1 ml-1" />
|
||||
</div>
|
||||
</button>
|
||||
{showAdvanced ? (
|
||||
@@ -119,7 +119,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
onChange={handleIsPrivateWorkspaceChange}
|
||||
/>
|
||||
<a href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" title="Learn more">
|
||||
<Icon type="info" className="color-neutral" />
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -127,7 +127,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
<>
|
||||
<DecoratedInput
|
||||
className={'mb-2'}
|
||||
left={[<Icon type="server" className="color-neutral" />]}
|
||||
left={[<Icon type="server" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Userphrase"
|
||||
value={privateWorkspaceUserphrase}
|
||||
@@ -136,7 +136,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
/>
|
||||
<DecoratedInput
|
||||
className={'mb-2'}
|
||||
left={[<Icon type="folder" className="color-neutral" />]}
|
||||
left={[<Icon type="folder" className="text-neutral" />]}
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={privateWorkspaceName}
|
||||
@@ -161,7 +161,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
rel="noopener noreferrer"
|
||||
title="Learn more"
|
||||
>
|
||||
<Icon type="info" className="color-neutral" />
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
@@ -175,7 +175,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
|
||||
/>
|
||||
<DecoratedInput
|
||||
type="text"
|
||||
left={[<Icon type="server" className="color-neutral" />]}
|
||||
left={[<Icon type="server" className="text-neutral" />]}
|
||||
placeholder="https://api.standardnotes.com"
|
||||
value={server}
|
||||
onChange={handleSyncServerChange}
|
||||
|
||||
@@ -105,34 +105,35 @@ const ConfirmPassword: FunctionComponent<Props> = ({
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
title="Go back"
|
||||
className="flex mr-2 color-neutral p-0"
|
||||
className="flex mr-2 text-neutral p-0"
|
||||
onClick={handleGoBack}
|
||||
focusable={true}
|
||||
disabled={isRegistering}
|
||||
/>
|
||||
<div className="sn-account-menu-headline">Confirm password</div>
|
||||
<div className="font-bold text-base">Confirm password</div>
|
||||
</div>
|
||||
<div className="px-3 mb-3 text-sm">
|
||||
Because your notes are encrypted using your password,{' '}
|
||||
<span className="color-danger">Standard Notes does not have a password reset option</span>. If you forget your
|
||||
<span className="text-danger">Standard Notes does not have a password reset option</span>. If you forget your
|
||||
password, you will permanently lose access to your data.
|
||||
</div>
|
||||
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
|
||||
<DecoratedPasswordInput
|
||||
className="mb-2"
|
||||
disabled={isRegistering}
|
||||
left={[<Icon type="password" className="color-neutral" />]}
|
||||
left={[<Icon type="password" className="text-neutral" />]}
|
||||
onChange={handlePasswordChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Confirm password"
|
||||
ref={passwordInputRef}
|
||||
value={confirmPassword}
|
||||
/>
|
||||
{error ? <div className="color-danger my-2">{error}</div> : null}
|
||||
{error ? <div className="text-danger my-2">{error}</div> : null}
|
||||
<Button
|
||||
className="btn-w-full mt-1 mb-3"
|
||||
primary
|
||||
fullWidth
|
||||
className="mt-1 mb-3"
|
||||
label={isRegistering ? 'Creating account...' : 'Create account & sign in'}
|
||||
variant="primary"
|
||||
onClick={handleConfirmFormSubmit}
|
||||
disabled={isRegistering}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import IconButton from '@/Components/Button/IconButton'
|
||||
import AdvancedOptions from './AdvancedOptions'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
|
||||
type Props = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -105,17 +106,17 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
title="Go back"
|
||||
className="flex mr-2 color-neutral p-0"
|
||||
className="flex mr-2 text-neutral p-0"
|
||||
onClick={handleClose}
|
||||
focusable={true}
|
||||
/>
|
||||
<div className="sn-account-menu-headline">Create account</div>
|
||||
<div className="font-bold text-base">Create account</div>
|
||||
</div>
|
||||
<form onSubmit={handleRegisterFormSubmit} className="px-3 mb-1">
|
||||
<DecoratedInput
|
||||
className="mb-2"
|
||||
disabled={isPrivateWorkspace}
|
||||
left={[<Icon type="email" className="color-neutral" />]}
|
||||
left={[<Icon type="email" className="text-neutral" />]}
|
||||
onChange={handleEmailChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Email"
|
||||
@@ -125,16 +126,16 @@ const CreateAccount: FunctionComponent<Props> = ({
|
||||
/>
|
||||
<DecoratedPasswordInput
|
||||
className="mb-2"
|
||||
left={[<Icon type="password" className="color-neutral" />]}
|
||||
left={[<Icon type="password" className="text-neutral" />]}
|
||||
onChange={handlePasswordChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Password"
|
||||
ref={passwordInputRef}
|
||||
value={password}
|
||||
/>
|
||||
<Button className="btn-w-full mt-1" label="Next" variant="primary" onClick={handleRegisterFormSubmit} />
|
||||
<Button className="mt-1" label="Next" primary onClick={handleRegisterFormSubmit} fullWidth={true} />
|
||||
</form>
|
||||
<div className="h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<AdvancedOptions
|
||||
application={application}
|
||||
viewControllerManager={viewControllerManager}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
||||
import WorkspaceSwitcherOption from './WorkspaceSwitcher/WorkspaceSwitcherOption'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { formatLastSyncDate } from '@/Utils/FormatLastSyncDate'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
|
||||
type Props = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -22,7 +23,7 @@ type Props = {
|
||||
closeMenu: () => void
|
||||
}
|
||||
|
||||
const iconClassName = 'color-neutral mr-2'
|
||||
const iconClassName = 'text-neutral mr-2'
|
||||
|
||||
const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
application,
|
||||
@@ -89,34 +90,34 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-3 mt-1 mb-1">
|
||||
<div className="sn-account-menu-headline">Account</div>
|
||||
<div className="font-bold text-base">Account</div>
|
||||
<div className="flex cursor-pointer" onClick={closeMenu}>
|
||||
<Icon type="close" className="color-neutral" />
|
||||
<Icon type="close" className="text-neutral" />
|
||||
</div>
|
||||
</div>
|
||||
{user ? (
|
||||
<>
|
||||
<div className="px-3 mb-3 color-foreground text-sm">
|
||||
<div className="px-3 mb-3 text-foreground text-sm">
|
||||
<div>You're signed in as:</div>
|
||||
<div className="my-0.5 font-bold wrap">{user.email}</div>
|
||||
<span className="color-neutral">{application.getHost()}</span>
|
||||
<span className="text-neutral">{application.getHost()}</span>
|
||||
</div>
|
||||
<div className="flex items-start justify-between px-3 mb-3">
|
||||
<div className="flex items-start justify-between px-3 mb-2">
|
||||
{isSyncingInProgress ? (
|
||||
<div className="flex items-center color-info font-semibold">
|
||||
<div className="sk-spinner w-5 h-5 mr-2 spinner-info"></div>
|
||||
<div className="flex items-center text-info text-sm font-semibold">
|
||||
<Spinner className="w-5 h-5 mr-2" />
|
||||
Syncing...
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start">
|
||||
<Icon type="check-circle" className="mr-2 success" />
|
||||
<Icon type="check-circle" className="mr-2 text-success" />
|
||||
<div>
|
||||
<div className="font-semibold success">Last synced:</div>
|
||||
<div className="color-text">{lastSyncDate}</div>
|
||||
<div className="font-semibold text-success text-sm">Last synced:</div>
|
||||
<div className="text-text text-sm">{lastSyncDate}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex cursor-pointer color-passive-1" onClick={doSynchronization}>
|
||||
<div className="flex cursor-pointer text-passive-1" onClick={doSynchronization}>
|
||||
<Icon type="sync" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,13 +125,13 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
) : (
|
||||
<>
|
||||
<div className="px-3 mb-1">
|
||||
<div className="mb-3 color-foreground">
|
||||
<div className="mb-3 text-foreground text-sm">
|
||||
You’re offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
|
||||
encryption.
|
||||
</div>
|
||||
<div className="flex items-center color-passive-1">
|
||||
<div className="flex items-center text-passive-1">
|
||||
<Icon type="cloud-off" className="mr-2" />
|
||||
<span className="font-semibold">Offline</span>
|
||||
<span className="font-semibold text-sm">Offline</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -169,7 +170,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
<Icon type="help" className={iconClassName} />
|
||||
Help & feedback
|
||||
</div>
|
||||
<span className="color-neutral">v{application.version}</span>
|
||||
<span className="text-neutral">v{application.version}</span>
|
||||
</MenuItem>
|
||||
{user ? (
|
||||
<>
|
||||
|
||||
@@ -11,6 +11,7 @@ import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import IconButton from '@/Components/Button/IconButton'
|
||||
import AdvancedOptions from './AdvancedOptions'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
|
||||
type Props = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -143,17 +144,17 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
title="Go back"
|
||||
className="flex mr-2 color-neutral p-0"
|
||||
className="flex mr-2 text-neutral p-0"
|
||||
onClick={() => setMenuPane(AccountMenuPane.GeneralMenu)}
|
||||
focusable={true}
|
||||
disabled={isSigningIn}
|
||||
/>
|
||||
<div className="sn-account-menu-headline">Sign in</div>
|
||||
<div className="font-bold text-base">Sign in</div>
|
||||
</div>
|
||||
<div className="px-3 mb-1">
|
||||
<DecoratedInput
|
||||
className={`mb-2 ${error ? 'border-danger' : null}`}
|
||||
left={[<Icon type="email" className="color-neutral" />]}
|
||||
left={[<Icon type="email" className="text-neutral" />]}
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
@@ -166,7 +167,7 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
<DecoratedPasswordInput
|
||||
className={`mb-2 ${error ? 'border-danger' : null}`}
|
||||
disabled={isSigningIn}
|
||||
left={[<Icon type="password" className="color-neutral" />]}
|
||||
left={[<Icon type="password" className="text-neutral" />]}
|
||||
onChange={handlePasswordChange}
|
||||
onFocus={resetInvalid}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -174,13 +175,14 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
ref={passwordInputRef}
|
||||
value={password}
|
||||
/>
|
||||
{error ? <div className="color-danger my-2">{error}</div> : null}
|
||||
{error ? <div className="text-danger my-2">{error}</div> : null}
|
||||
<Button
|
||||
className="btn-w-full mt-1 mb-3"
|
||||
className="mt-1 mb-3"
|
||||
label={isSigningIn ? 'Signing in...' : 'Sign in'}
|
||||
variant="primary"
|
||||
primary
|
||||
onClick={handleSignInFormSubmit}
|
||||
disabled={isSigningIn}
|
||||
fullWidth={true}
|
||||
/>
|
||||
<Checkbox
|
||||
name="is-ephemeral"
|
||||
@@ -199,7 +201,7 @@ const SignInPane: FunctionComponent<Props> = ({ application, viewControllerManag
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<AdvancedOptions
|
||||
viewControllerManager={viewControllerManager}
|
||||
application={application}
|
||||
|
||||
@@ -58,7 +58,7 @@ const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<MenuItem
|
||||
type={MenuItemType.RadioButton}
|
||||
className="sn-dropdown-item py-2 focus:bg-info-backdrop focus:shadow-none"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-2 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={onClick}
|
||||
checked={descriptor.primary}
|
||||
>
|
||||
@@ -76,7 +76,7 @@ const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
<div>{descriptor.label}</div>
|
||||
)}
|
||||
{descriptor.primary && !hideOptions && (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
role="button"
|
||||
className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer"
|
||||
@@ -85,7 +85,7 @@ const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
setIsRenaming((isRenaming) => !isRenaming)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="sn-icon--mid color-neutral" />
|
||||
<Icon type="pencil" className="text-neutral" size="medium" />
|
||||
</a>
|
||||
<a
|
||||
role="button"
|
||||
@@ -95,7 +95,7 @@ const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
onDelete()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="sn-icon--mid color-danger" />
|
||||
<Icon type="trash" className="text-danger" size="medium" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -78,13 +78,13 @@ const WorkspaceSwitcherMenu: FunctionComponent<Props> = ({
|
||||
void mainApplicationGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
}}
|
||||
>
|
||||
<Icon type="user-add" className="color-neutral mr-2" />
|
||||
<Icon type="user-add" className="text-neutral mr-2" />
|
||||
Add another workspace
|
||||
</MenuItem>
|
||||
|
||||
{!hideWorkspaceOptions && (
|
||||
<MenuItem type={MenuItemType.IconButton} onClick={signoutAll}>
|
||||
<Icon type="signOut" className="color-neutral mr-2" />
|
||||
<Icon type="signOut" className="text-neutral mr-2" />
|
||||
Sign out all workspaces
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import WorkspaceSwitcherMenu from './WorkspaceSwitcherMenu'
|
||||
import MenuItem from '@/Components/Menu/MenuItem'
|
||||
import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
@@ -43,21 +45,25 @@ const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGrou
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||
<MenuItem
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
role="menuitem"
|
||||
ref={buttonRef}
|
||||
type={MenuItemType.IconButton}
|
||||
onClick={toggleMenu}
|
||||
className="justify-between"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||
<Icon type="user-switch" className="text-neutral mr-2" />
|
||||
Switch workspace
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
</button>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</MenuItem>
|
||||
{isOpen && (
|
||||
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="bg-default rounded shadow-main max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
|
||||
@@ -84,7 +84,7 @@ class ApplicationGroupView extends Component<Props, State> {
|
||||
<DialogContent
|
||||
aria-label="Switching workspace"
|
||||
className={
|
||||
'challenge-modal flex flex-col items-center bg-default p-8 rounded relative shadow-overlay-light border-1 border-solid border-main'
|
||||
'challenge-modal flex flex-col items-center bg-default p-8 rounded relative shadow-overlay-light border border-solid border-border'
|
||||
}
|
||||
>
|
||||
{message}
|
||||
|
||||
@@ -270,12 +270,14 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
className={`sn-icon-button border-contrast ${attachedFilesCount > 0 ? 'py-1 px-3' : ''}`}
|
||||
className={`flex justify-center items-center min-w-8 h-8 bg-text-padding hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer ${
|
||||
attachedFilesCount > 0 ? 'py-1 px-3' : ''
|
||||
}`}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<VisuallyHidden>Attached files</VisuallyHidden>
|
||||
<Icon type="attachment-file" className="block" />
|
||||
{attachedFilesCount > 0 && <span className="ml-2">{attachedFilesCount}</span>}
|
||||
{attachedFilesCount > 0 && <span className="text-sm ml-2">{attachedFilesCount}</span>}
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyDown={(event) => {
|
||||
@@ -289,7 +291,7 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
||||
className="bg-default rounded shadow-main transition-transform duration-150 slide-down-animation min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
{open && (
|
||||
|
||||
@@ -78,12 +78,12 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
border: isDraggingFiles ? '2px dashed var(--sn-stylekit-info-color)' : '',
|
||||
}}
|
||||
>
|
||||
<div className="flex border-0 border-b-1 border-solid border-main">
|
||||
<div className="flex border-b border-solid border-border">
|
||||
<button
|
||||
id={PopoverTabs.AttachedFiles}
|
||||
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom ${
|
||||
currentTab === PopoverTabs.AttachedFiles ? 'color-info font-medium shadow-bottom' : 'color-text'
|
||||
} ${attachedTabDisabled ? 'color-neutral cursor-not-allowed' : ''}`}
|
||||
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom text-sm ${
|
||||
currentTab === PopoverTabs.AttachedFiles ? 'text-info font-medium shadow-bottom' : 'text-text'
|
||||
} ${attachedTabDisabled ? 'text-neutral cursor-not-allowed' : ''}`}
|
||||
onClick={() => {
|
||||
setCurrentTab(PopoverTabs.AttachedFiles)
|
||||
}}
|
||||
@@ -94,8 +94,8 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
</button>
|
||||
<button
|
||||
id={PopoverTabs.AllFiles}
|
||||
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom ${
|
||||
currentTab === PopoverTabs.AllFiles ? 'color-info font-medium shadow-bottom' : 'color-text'
|
||||
className={`bg-default border-0 cursor-pointer px-3 py-2.5 relative focus:bg-info-backdrop focus:shadow-bottom text-sm ${
|
||||
currentTab === PopoverTabs.AllFiles ? 'text-info font-medium shadow-bottom' : 'text-text'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCurrentTab(PopoverTabs.AllFiles)
|
||||
@@ -107,11 +107,11 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
</div>
|
||||
<div className="min-h-0 max-h-110 overflow-y-auto">
|
||||
{filteredList.length > 0 || searchQuery.length > 0 ? (
|
||||
<div className="sticky top-0 left-0 p-3 bg-default border-0 border-b-1 border-solid border-main">
|
||||
<div className="sticky top-0 left-0 p-3 bg-default border-b border-solid border-border">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
className="color-text w-full rounded py-1.5 px-3 text-input bg-default border-solid border-1 border-main"
|
||||
className="text-text w-full rounded py-1.5 px-3 text-sm bg-default border-solid border border-border"
|
||||
placeholder="Search files..."
|
||||
value={searchQuery}
|
||||
onInput={(e) => {
|
||||
@@ -129,7 +129,7 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Icon type="clear-circle-filled" className="color-neutral" />
|
||||
<Icon type="clear-circle-filled" className="text-neutral" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -161,20 +161,20 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
? 'No files attached to this note'
|
||||
: 'No files found in this account'}
|
||||
</div>
|
||||
<Button variant="normal" onClick={handleAttachFilesClick} onBlur={closeOnBlur}>
|
||||
<Button onClick={handleAttachFilesClick} onBlur={closeOnBlur}>
|
||||
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
|
||||
</Button>
|
||||
<div className="text-xs color-passive-0 mt-3">Or drop your files here</div>
|
||||
<div className="text-xs text-passive-0 mt-3">Or drop your files here</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{filteredList.length > 0 && (
|
||||
<button
|
||||
className="sn-dropdown-item py-3 border-0 border-t-1px border-solid border-main focus:bg-info-backdrop"
|
||||
className="flex items-center cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-3 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm border-0 border-t border-solid border-border"
|
||||
onClick={handleAttachFilesClick}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Icon type="add" className="mr-2 color-neutral" />
|
||||
<Icon type="add" className="mr-2 text-neutral" />
|
||||
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -91,7 +91,7 @@ const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
||||
{isRenamingFile ? (
|
||||
<input
|
||||
type="text"
|
||||
className="text-input px-1.5 py-1 mb-1 border-1 border-solid border-main bg-transparent color-foreground"
|
||||
className="text-input px-1.5 py-1 mb-1 border border-solid border-border bg-transparent text-foreground"
|
||||
value={fileName}
|
||||
ref={fileNameInputRef}
|
||||
onInput={handleFileNameInput}
|
||||
@@ -100,13 +100,13 @@ const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
||||
/>
|
||||
) : (
|
||||
<div className="text-sm mb-1 break-word">
|
||||
<span className="vertical-middle">{file.name}</span>
|
||||
<span className="align-middle">{file.name}</span>
|
||||
{file.protected && (
|
||||
<Icon type="lock-filled" className="sn-icon--small ml-2 color-neutral vertical-middle" />
|
||||
<Icon type="lock-filled" className="ml-2 text-neutral inline align-middle" size="small" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs color-passive-0">
|
||||
<div className="text-xs text-passive-0">
|
||||
{file.created_at.toLocaleString()} · {formatSizeToReadableString(file.decryptedSize)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Switch from '@/Components/Switch/Switch'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { PopoverFileSubmenuProps } from './PopoverFileItemProps'
|
||||
import { PopoverFileItemActionType } from './PopoverFileItemAction'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
|
||||
const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
file,
|
||||
@@ -67,7 +68,7 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
onBlur={closeOnBlur}
|
||||
className="w-7 h-7 p-1 rounded-full border-0 bg-transparent hover:bg-contrast cursor-pointer"
|
||||
>
|
||||
<Icon type="more" className="color-neutral" />
|
||||
<Icon type="more" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
@@ -75,25 +76,27 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className="sn-dropdown flex flex-col max-h-120 min-w-60 py-1 fixed overflow-y-auto"
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} flex-col bg-default rounded shadow-main max-h-120 min-w-60 py-1 fixed overflow-y-auto`}
|
||||
>
|
||||
{isMenuOpen && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
previewHandler(file)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="file" className="mr-2 color-neutral" />
|
||||
<Icon type="file" className="mr-2 text-neutral" />
|
||||
Preview file
|
||||
</button>
|
||||
{isAttachedToNote ? (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DetachFileToNote,
|
||||
@@ -102,13 +105,13 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link-off" className="mr-2 color-neutral" />
|
||||
<Icon type="link-off" className="mr-2 text-neutral" />
|
||||
Detach from note
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.AttachFileToNote,
|
||||
@@ -117,13 +120,13 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link" className="mr-2 color-neutral" />
|
||||
<Icon type="link" className="mr-2 text-neutral" />
|
||||
Attach to note
|
||||
</button>
|
||||
)}
|
||||
<div className="min-h-1px my-1 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
className="sn-dropdown-item justify-between focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm justify-between focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.ToggleFileProtection,
|
||||
@@ -136,7 +139,7 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className="mr-2 color-neutral" />
|
||||
<Icon type="password" className="mr-2 text-neutral" />
|
||||
Password protection
|
||||
</span>
|
||||
<Switch
|
||||
@@ -145,10 +148,10 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
checked={isFileProtected}
|
||||
/>
|
||||
</button>
|
||||
<div className="min-h-1px my-1 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DownloadFile,
|
||||
@@ -157,22 +160,22 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="download" className="mr-2 color-neutral" />
|
||||
<Icon type="download" className="mr-2 text-neutral" />
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
setIsRenamingFile(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="mr-2 color-neutral" />
|
||||
<Icon type="pencil" className="mr-2 text-neutral" />
|
||||
Rename
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:shadow-none text-sm focus:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DeleteFile,
|
||||
@@ -181,9 +184,14 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="mr-2 color-danger" />
|
||||
<span className="color-danger">Delete permanently</span>
|
||||
<Icon type="trash" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</button>
|
||||
<div className="px-3 py-1 text-xs text-neutral font-medium">
|
||||
<div>
|
||||
<span className="font-semibold">File ID:</span> {file.uuid}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
|
||||
@@ -7,9 +7,9 @@ type Props = {
|
||||
}
|
||||
|
||||
const styles = {
|
||||
base: 'px-2 py-1.5 text-center rounded-full cursor-pointer transition border-1 border-solid active:border-info active:bg-info active:color-neutral-contrast',
|
||||
unselected: 'color-neutral border-secondary',
|
||||
selected: 'border-info bg-info color-neutral-contrast',
|
||||
base: 'px-2 py-1 text-center rounded-full cursor-pointer transition border border-solid active:border-info active:bg-info active:text-neutral-contrast',
|
||||
unselected: 'text-neutral border-secondary-border',
|
||||
selected: 'border-info bg-info text-neutral-contrast',
|
||||
}
|
||||
|
||||
const Bubble: FunctionComponent<Props> = ({ label, selected, onSelect }) => (
|
||||
|
||||
@@ -1,52 +1,99 @@
|
||||
import { Ref, forwardRef, ReactNode, ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
const baseClass = 'rounded px-4 py-1.75 font-bold text-sm fit-content'
|
||||
type ButtonStyle = 'default' | 'contrast' | 'neutral' | 'info' | 'warning' | 'danger' | 'success'
|
||||
|
||||
type ButtonVariant = 'normal' | 'primary'
|
||||
|
||||
const getClassName = (variant: ButtonVariant, danger: boolean, disabled: boolean) => {
|
||||
const borders = variant === 'normal' ? 'border-solid border-main border-1' : 'no-border'
|
||||
const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||
|
||||
let colors = variant === 'normal' ? 'bg-default color-text' : 'bg-info color-info-contrast'
|
||||
|
||||
let focusHoverStates =
|
||||
variant === 'normal'
|
||||
? 'focus:bg-contrast focus:outline-none hover:bg-contrast'
|
||||
: 'hover:brightness-130 focus:outline-none focus:brightness-130'
|
||||
|
||||
if (danger) {
|
||||
colors = variant === 'normal' ? 'bg-default color-danger' : 'bg-danger color-info-contrast'
|
||||
const getColorsForNormalVariant = (style: ButtonStyle) => {
|
||||
switch (style) {
|
||||
case 'default':
|
||||
return 'bg-default text-text'
|
||||
case 'contrast':
|
||||
return 'bg-default text-contrast'
|
||||
case 'neutral':
|
||||
return 'bg-default text-neutral'
|
||||
case 'info':
|
||||
return 'bg-default text-info'
|
||||
case 'warning':
|
||||
return 'bg-default text-warning'
|
||||
case 'danger':
|
||||
return 'bg-default text-danger'
|
||||
case 'success':
|
||||
return 'bg-default text-success'
|
||||
}
|
||||
}
|
||||
|
||||
const getColorsForPrimaryVariant = (style: ButtonStyle) => {
|
||||
switch (style) {
|
||||
case 'default':
|
||||
return 'bg-default text-foreground'
|
||||
case 'contrast':
|
||||
return 'bg-contrast text-text'
|
||||
case 'neutral':
|
||||
return 'bg-neutral text-neutral-contrast'
|
||||
case 'info':
|
||||
return 'bg-info text-info-contrast'
|
||||
case 'warning':
|
||||
return 'bg-warning text-warning-contrast'
|
||||
case 'danger':
|
||||
return 'bg-danger text-danger-contrast'
|
||||
case 'success':
|
||||
return 'bg-success text-success-contrast'
|
||||
}
|
||||
}
|
||||
|
||||
const getClassName = (
|
||||
primary: boolean,
|
||||
style: ButtonStyle,
|
||||
disabled: boolean,
|
||||
fullWidth?: boolean,
|
||||
small?: boolean,
|
||||
isRounded?: boolean,
|
||||
) => {
|
||||
const borders = primary ? 'no-border' : 'border-solid border-border border'
|
||||
const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||
const width = fullWidth ? 'w-full' : 'w-fit'
|
||||
const padding = small ? 'px-3 py-1.5' : 'px-4 py-1.5'
|
||||
const textSize = small ? 'text-xs' : 'text-sm'
|
||||
const rounded = isRounded ? 'rounded' : ''
|
||||
|
||||
let colors = primary ? getColorsForPrimaryVariant(style) : getColorsForNormalVariant(style)
|
||||
|
||||
let focusHoverStates = primary
|
||||
? 'hover:brightness-125 focus:outline-none focus:brightness-125'
|
||||
: 'focus:bg-contrast focus:outline-none hover:bg-contrast'
|
||||
|
||||
if (disabled) {
|
||||
colors = variant === 'normal' ? 'bg-default color-passive-2' : 'bg-passive-2 color-info-contrast'
|
||||
focusHoverStates =
|
||||
variant === 'normal'
|
||||
? 'focus:bg-default focus:outline-none hover:bg-default'
|
||||
: 'focus:brightness-default focus:outline-none hover:brightness-default'
|
||||
colors = primary ? 'bg-passive-2 text-info-contrast' : 'bg-default text-passive-2'
|
||||
focusHoverStates = primary
|
||||
? 'focus:brightness-100 focus:outline-none hover:brightness-100'
|
||||
: 'focus:bg-default focus:outline-none hover:bg-default'
|
||||
}
|
||||
|
||||
return `${baseClass} ${colors} ${borders} ${focusHoverStates} ${cursor}`
|
||||
return `${rounded} font-bold ${width} ${padding} ${textSize} ${colors} ${borders} ${focusHoverStates} ${cursor}`
|
||||
}
|
||||
|
||||
interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
variant?: ButtonVariant
|
||||
dangerStyle?: boolean
|
||||
primary?: boolean
|
||||
colorStyle?: ButtonStyle
|
||||
label?: string
|
||||
fullWidth?: boolean
|
||||
small?: boolean
|
||||
rounded?: boolean
|
||||
}
|
||||
|
||||
const Button = forwardRef(
|
||||
(
|
||||
{
|
||||
variant = 'normal',
|
||||
primary = false,
|
||||
label,
|
||||
className = '',
|
||||
dangerStyle: danger = false,
|
||||
colorStyle = primary ? 'info' : 'default',
|
||||
disabled = false,
|
||||
children,
|
||||
fullWidth,
|
||||
small,
|
||||
rounded = true,
|
||||
...props
|
||||
}: ButtonProps,
|
||||
ref: Ref<HTMLButtonElement>,
|
||||
@@ -54,7 +101,7 @@ const Button = forwardRef(
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`${getClassName(variant, danger, disabled)} ${className}`}
|
||||
className={`${getClassName(primary, colorStyle, disabled, fullWidth, small, rounded)} ${className}`}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
{...props}
|
||||
|
||||
@@ -18,7 +18,12 @@ const RoundIconButton: FunctionComponent<Props> = ({ onClick, type, className, i
|
||||
}
|
||||
const classes = type === 'primary' ? 'info ' : ''
|
||||
return (
|
||||
<button className={`sn-icon-button ${classes} ${className ?? ''}`} onClick={click}>
|
||||
<button
|
||||
className={`text-neutral min-w-8 h-8 flex justify-center items-center border-solid border border-border bg-clip-padding m-0 bg-transparent cursor-pointer rounded-full hover:text-text focus:text-text hover:bg-contrast focus:bg-contrast focus:outline-none focus:ring-info ${classes} ${
|
||||
className ?? ''
|
||||
}`}
|
||||
onClick={click}
|
||||
>
|
||||
<Icon type={iconType} />
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -186,7 +186,7 @@ const ChallengeModal: FunctionComponent<Props> = ({
|
||||
aria-label="Challenge modal"
|
||||
className={`challenge-modal flex flex-col items-center bg-default p-8 rounded relative ${
|
||||
challenge.reason !== ChallengeReason.ApplicationUnlock
|
||||
? 'shadow-overlay-light border-1 border-solid border-main'
|
||||
? 'shadow-overlay-light border border-solid border-border'
|
||||
: 'focus:shadow-none'
|
||||
}`}
|
||||
>
|
||||
@@ -196,7 +196,7 @@ const ChallengeModal: FunctionComponent<Props> = ({
|
||||
aria-label="Close modal"
|
||||
className="flex p-1 bg-transparent border-0 cursor-pointer absolute top-4 right-4"
|
||||
>
|
||||
<Icon type="close" className="color-neutral" />
|
||||
<Icon type="close" className="text-neutral" />
|
||||
</button>
|
||||
)}
|
||||
<ProtectedIllustration className="w-30 h-30 mb-4" />
|
||||
@@ -224,7 +224,7 @@ const ChallengeModal: FunctionComponent<Props> = ({
|
||||
/>
|
||||
))}
|
||||
</form>
|
||||
<Button variant="primary" disabled={isProcessing} className="min-w-76 mt-1 mb-3.5" onClick={submit}>
|
||||
<Button primary disabled={isProcessing} className="min-w-76 mt-1 mb-3.5" onClick={submit}>
|
||||
{isProcessing ? 'Generating Keys...' : 'Submit'}
|
||||
</Button>
|
||||
{shouldShowForgotPasscode && (
|
||||
@@ -250,7 +250,7 @@ const ChallengeModal: FunctionComponent<Props> = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Icon type="help" className="mr-2 color-neutral" />
|
||||
<Icon type="help" className="mr-2 text-neutral" />
|
||||
Forgot passcode?
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -38,8 +38,8 @@ const ChallengeModalPrompt: FunctionComponent<Props> = ({ prompt, values, index,
|
||||
return (
|
||||
<label
|
||||
key={option.label}
|
||||
className={`cursor-pointer px-2 py-1.5 rounded ${
|
||||
selected ? 'bg-default color-foreground font-semibold' : 'color-passive-0 hover:bg-passive-3'
|
||||
className={`cursor-pointer px-2 py-1.5 rounded focus-within:ring-2 focus-within:ring-info ${
|
||||
selected ? 'bg-default text-foreground font-semibold' : 'text-passive-0 hover:bg-passive-3'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
@@ -76,7 +76,7 @@ const ChallengeModalPrompt: FunctionComponent<Props> = ({ prompt, values, index,
|
||||
onChange={(value) => onValueChange(value, prompt)}
|
||||
/>
|
||||
)}
|
||||
{isInvalid && <div className="text-sm color-danger mt-2">Invalid authentication, please try again.</div>}
|
||||
{isInvalid && <div className="text-sm text-danger mt-2">Invalid authentication, please try again.</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,11 +48,15 @@ const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({ mainApplication
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<Button ref={buttonRef} onClick={toggleMenu} className="flex items-center justify-center min-w-76 mt-2">
|
||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||
<Icon type="user-switch" className="text-neutral mr-2" />
|
||||
Switch workspace
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="bg-default rounded-md shadow-main max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
|
||||
@@ -72,7 +72,7 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="sn-icon-button border-contrast"
|
||||
className="flex justify-center items-center min-w-8 h-8 hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer"
|
||||
>
|
||||
<VisuallyHidden>Change note type</VisuallyHidden>
|
||||
<Icon type="dashboard" className="block" />
|
||||
@@ -89,7 +89,7 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown--animated min-w-68 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
||||
className="bg-default rounded shadow-main transition-transform duration-150 slide-down-animation min-w-68 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
{isOpen && (
|
||||
|
||||
@@ -183,7 +183,7 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
|
||||
return (
|
||||
<Fragment key={groupId}>
|
||||
<div className={`py-1 border-0 border-t-1px border-solid border-main ${index === 0 ? 'border-t-0' : ''}`}>
|
||||
<div className={`py-1 border-0 border-t border-solid border-border ${index === 0 ? 'border-t-0' : ''}`}>
|
||||
{group.items.map((item) => {
|
||||
const onClickEditorItem = () => {
|
||||
selectEditor(item).catch(console.error)
|
||||
@@ -193,11 +193,9 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
key={item.name}
|
||||
type={MenuItemType.RadioButton}
|
||||
onClick={onClickEditorItem}
|
||||
className={
|
||||
'sn-dropdown-item py-2 text-input focus:bg-info-backdrop focus:shadow-none flex-row-reverse'
|
||||
}
|
||||
className={'py-2 flex-row-reverse'}
|
||||
onBlur={closeOnBlur}
|
||||
checked={isSelectedEditor(item)}
|
||||
checked={item.isEntitled ? isSelectedEditor(item) : undefined}
|
||||
>
|
||||
<div className="flex flex-grow items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -75,49 +75,49 @@ export const createEditorMenuGroups = (application: WebApplication, editors: SNC
|
||||
const editorMenuGroups: EditorMenuGroup[] = [
|
||||
{
|
||||
icon: 'plain-text',
|
||||
iconClassName: 'color-accessory-tint-1',
|
||||
iconClassName: 'text-accessory-tint-1',
|
||||
title: 'Plain text',
|
||||
items: editorItems.plain,
|
||||
},
|
||||
{
|
||||
icon: 'rich-text',
|
||||
iconClassName: 'color-accessory-tint-1',
|
||||
iconClassName: 'text-accessory-tint-1',
|
||||
title: 'Rich text',
|
||||
items: editorItems['rich-text'],
|
||||
},
|
||||
{
|
||||
icon: 'markdown',
|
||||
iconClassName: 'color-accessory-tint-2',
|
||||
iconClassName: 'text-accessory-tint-2',
|
||||
title: 'Markdown text',
|
||||
items: editorItems.markdown,
|
||||
},
|
||||
{
|
||||
icon: 'tasks',
|
||||
iconClassName: 'color-accessory-tint-3',
|
||||
iconClassName: 'text-accessory-tint-3',
|
||||
title: 'Todo',
|
||||
items: editorItems.task,
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
iconClassName: 'color-accessory-tint-4',
|
||||
iconClassName: 'text-accessory-tint-4',
|
||||
title: 'Code',
|
||||
items: editorItems.code,
|
||||
},
|
||||
{
|
||||
icon: 'spreadsheets',
|
||||
iconClassName: 'color-accessory-tint-5',
|
||||
iconClassName: 'text-accessory-tint-5',
|
||||
title: 'Spreadsheet',
|
||||
items: editorItems.spreadsheet,
|
||||
},
|
||||
{
|
||||
icon: 'authenticator',
|
||||
iconClassName: 'color-accessory-tint-6',
|
||||
iconClassName: 'text-accessory-tint-6',
|
||||
title: 'Authentication',
|
||||
items: editorItems.authentication,
|
||||
},
|
||||
{
|
||||
icon: 'editor',
|
||||
iconClassName: 'color-neutral',
|
||||
iconClassName: 'text-neutral',
|
||||
title: 'Others',
|
||||
items: editorItems.others,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ type CheckboxProps = {
|
||||
|
||||
const Checkbox: FunctionComponent<CheckboxProps> = ({ name, checked, onChange, disabled, label }) => {
|
||||
return (
|
||||
<label htmlFor={name} className="flex items-center fit-content mb-2">
|
||||
<label htmlFor={name} className="flex items-center fit-content mb-2 text-sm">
|
||||
<input
|
||||
className="mr-2"
|
||||
type="checkbox"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
deprecationMessage: string | undefined
|
||||
@@ -8,16 +9,18 @@ type Props = {
|
||||
const IsDeprecated: FunctionComponent<Props> = ({ deprecationMessage, dismissDeprecationMessage }) => {
|
||||
return (
|
||||
<div className={'sn-component'}>
|
||||
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
|
||||
<div className="flex justify-between items-center w-full min-h-[1.625rem] py-2.5 px-2 bg-contrast text-text border-b border-border select-none">
|
||||
<div className={'left'}>
|
||||
<div className={'sk-app-bar-item'}>
|
||||
<div className={'sk-label warning'}>{deprecationMessage || 'This extension is deprecated.'}</div>
|
||||
<div className="font-bold text-xs text-warning">
|
||||
{deprecationMessage || 'This extension is deprecated.'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'right'}>
|
||||
<div className={'sk-app-bar-item'} onClick={dismissDeprecationMessage}>
|
||||
<button className={'sn-button small info'}>Dismiss</button>
|
||||
</div>
|
||||
<Button primary onClick={dismissDeprecationMessage} small>
|
||||
Dismiss
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FeatureStatus } from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||
|
||||
type Props = {
|
||||
expiredDate: string
|
||||
@@ -24,24 +26,20 @@ const statusString = (featureStatus: FeatureStatus, expiredDate: string, compone
|
||||
const IsExpired: FunctionComponent<Props> = ({ expiredDate, featureStatus, componentName, manageSubscription }) => {
|
||||
return (
|
||||
<div className={'sn-component'}>
|
||||
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
|
||||
<div className="flex justify-between items-center w-full min-h-[1.625rem] py-2.5 px-2 bg-contrast text-text border-b border-border select-none">
|
||||
<div className={'left'}>
|
||||
<div className={'sk-app-bar-item'}>
|
||||
<div className={'sk-app-bar-item-column'}>
|
||||
<div className={'sk-circle danger small'} />
|
||||
</div>
|
||||
<div className={'sk-app-bar-item-column'}>
|
||||
<div>
|
||||
<strong>{statusString(featureStatus, expiredDate, componentName)}</strong>
|
||||
<div className={'sk-p'}>{componentName} is in a read-only state.</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<IndicatorCircle style="danger" />
|
||||
<div className="ml-2">
|
||||
<strong>{statusString(featureStatus, expiredDate, componentName)}</strong>
|
||||
<div className={'sk-p'}>{componentName} is in a read-only state.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'right'}>
|
||||
<div className={'sk-app-bar-item'} onClick={() => manageSubscription()}>
|
||||
<button className={'sn-button small success'}>Manage Subscription</button>
|
||||
</div>
|
||||
<Button onClick={manageSubscription} primary colorStyle="success" small>
|
||||
Manage subscription
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
componentName: string
|
||||
@@ -8,16 +9,16 @@ type Props = {
|
||||
const IssueOnLoading: FunctionComponent<Props> = ({ componentName, reloadIframe }) => {
|
||||
return (
|
||||
<div className={'sn-component'}>
|
||||
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
|
||||
<div className="flex justify-between items-center w-full min-h-[1.625rem] py-2.5 px-2 bg-contrast text-text border-b border-border select-none">
|
||||
<div className={'left'}>
|
||||
<div className={'sk-app-bar-item'}>
|
||||
<div className={'sk-label.warning'}>There was an issue loading {componentName}.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'right'}>
|
||||
<div className={'sk-app-bar-item'} onClick={reloadIframe}>
|
||||
<button className={'sn-button small info'}>Reload</button>
|
||||
</div>
|
||||
<Button primary onClick={reloadIframe} small>
|
||||
Reload
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,14 +7,14 @@ const OfflineRestricted: FunctionComponent = () => {
|
||||
<div className={'sk-panel-content'}>
|
||||
<div className={'sk-panel-section stretch'}>
|
||||
<div className={'sk-panel-column'} />
|
||||
<div className={'sk-h1 sk-bold'}>You have restricted this component to not use a hosted version.</div>
|
||||
<div className="font-bold text-base">You have restricted this component to not use a hosted version.</div>
|
||||
<div className={'sk-subtitle'}>Locally-installed components are not available in the web application.</div>
|
||||
<div className={'sk-panel-row'} />
|
||||
<div className={'sk-panel-row'}>
|
||||
<div className={'sk-panel-column'}>
|
||||
<div className={'sk-p'}>To continue, choose from the following options:</div>
|
||||
<ul>
|
||||
<li className={'sk-p'}>
|
||||
<ul className="list-disc pl-8 mt-3">
|
||||
<li className="sk-p mb-1">
|
||||
Enable the Hosted option for this component by opening the Preferences {'>'} General {'>'} Advanced
|
||||
Settings menu and toggling 'Use hosted when local is unavailable' under this component's options.
|
||||
Then press Reload.
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -31,7 +32,7 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, viewContro
|
||||
const showWorkspaceWarning = workspaces.length > 1 && isDesktopApplication()
|
||||
|
||||
return (
|
||||
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
|
||||
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef} className="p-0 max-w-[600px]">
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
@@ -40,11 +41,11 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, viewContro
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title">Sign out workspace?</AlertDialogLabel>
|
||||
<AlertDialogDescription className="sk-panel-row">
|
||||
<div>
|
||||
<p className="color-foreground">{STRING_SIGN_OUT_CONFIRMATION}</p>
|
||||
<p className="text-foreground">{STRING_SIGN_OUT_CONFIRMATION}</p>
|
||||
{showWorkspaceWarning && (
|
||||
<>
|
||||
<br />
|
||||
<p className="color-foreground">
|
||||
<p className="text-foreground">
|
||||
<strong>Note: </strong>
|
||||
Because you have other workspaces signed in, this sign out may leave logs and other metadata
|
||||
of your session on this device. For a more robust sign out that performs a hard clear of all
|
||||
@@ -82,12 +83,15 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, viewContro
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex my-1 mt-4">
|
||||
<button className="sn-button small neutral" ref={cancelRef} onClick={closeDialog}>
|
||||
<div className="flex my-1 mt-4 gap-2">
|
||||
<Button primary small colorStyle="neutral" rounded={false} ref={cancelRef} onClick={closeDialog}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="sn-button small danger ml-2"
|
||||
</Button>
|
||||
<Button
|
||||
primary
|
||||
small
|
||||
colorStyle="danger"
|
||||
rounded={false}
|
||||
onClick={() => {
|
||||
if (deleteLocalBackups) {
|
||||
application.signOutAndDeleteLocalBackups().catch(console.error)
|
||||
@@ -98,7 +102,7 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, viewContro
|
||||
}}
|
||||
>
|
||||
{application.hasAccount() ? 'Sign Out' : 'Delete Workspace'}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="infinite-scroll border-solid border-0 border-t-1px border-main focus:shadow-none focus:outline-none"
|
||||
className="infinite-scroll border-solid border-t border-border focus:shadow-none focus:outline-none"
|
||||
id={ElementIds.ContentList}
|
||||
onScroll={onScroll}
|
||||
onKeyDown={onKeyDown}
|
||||
|
||||
@@ -255,8 +255,8 @@ const ContentListView: FunctionComponent<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">No items.</p> : null}
|
||||
{!completedFullSync && !renderedItems.length ? <p className="empty-items-list faded">Loading...</p> : null}
|
||||
{completedFullSync && !renderedItems.length ? <p className="empty-items-list opacity-50">No items.</p> : null}
|
||||
{!completedFullSync && !renderedItems.length ? <p className="empty-items-list opacity-50">Loading...</p> : null}
|
||||
{renderedItems.length ? (
|
||||
<ContentList
|
||||
items={renderedItems}
|
||||
|
||||
@@ -53,8 +53,8 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`content-list-item flex items-stretch w-full cursor-pointer ${
|
||||
selected && 'selected border-0 border-l-2px border-solid border-info'
|
||||
className={`content-list-item flex items-stretch w-full cursor-pointer text-text ${
|
||||
selected && 'selected border-l-2px border-solid border-info'
|
||||
}`}
|
||||
id={item.uuid}
|
||||
onClick={onClick}
|
||||
@@ -70,8 +70,8 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
) : (
|
||||
<div className="pr-4" />
|
||||
)}
|
||||
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
|
||||
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
|
||||
<div className="flex-grow min-w-0 py-4 px-0 border-b border-solid border-border">
|
||||
<div className="flex items-start justify-between font-semibold text-base leading-[1.3] overflow-hidden">
|
||||
<div className="break-word mr-2">{item.title}</div>
|
||||
</div>
|
||||
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
|
||||
|
||||
@@ -45,16 +45,16 @@ const ContentListHeader = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="section-title-bar-header">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-lg font-semibold title">{panelTitle}</div>
|
||||
{optionsSubtitle && <div className="text-xs color-passive-0">{optionsSubtitle}</div>}
|
||||
<div className="section-title-bar-header gap-1">
|
||||
<div className="flex flex-col flex-grow">
|
||||
<div className="text-lg font-semibold text-text">{panelTitle}</div>
|
||||
{optionsSubtitle && <div className="text-xs text-passive-0">{optionsSubtitle}</div>}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="relative" ref={displayOptionsContainerRef}>
|
||||
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
|
||||
<StyledDisplayOptionsButton pressed={showDisplayOptionsMenu} ref={displayOptionsButtonRef}>
|
||||
<Icon type="sort-descending" className="w-5 h-5" />
|
||||
<StyledDisplayOptionsButton $pressed={showDisplayOptionsMenu} ref={displayOptionsButtonRef}>
|
||||
<Icon type="sort-descending" />
|
||||
</StyledDisplayOptionsButton>
|
||||
<DisclosurePanel>
|
||||
{showDisplayOptionsMenu && displayOptionsMenuPosition && (
|
||||
@@ -72,12 +72,12 @@ const ContentListHeader = ({
|
||||
</Disclosure>
|
||||
</div>
|
||||
<button
|
||||
className="flex justify-center items-center min-w-8 h-8 ml-3 bg-info hover:brightness-130 color-info-contrast border-1 border-solid border-transparent rounded-full cursor-pointer"
|
||||
className="flex justify-center items-center min-w-8 h-8 ml-3 bg-info hover:brightness-125 text-info-contrast border border-solid border-transparent rounded-full cursor-pointer"
|
||||
title={addButtonLabel}
|
||||
aria-label={addButtonLabel}
|
||||
onClick={addNewItem}
|
||||
>
|
||||
<Icon type="add" className="w-5 h-5" />
|
||||
<Icon type="add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,15 +99,15 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
return (
|
||||
<Menu
|
||||
className={
|
||||
'py-1 sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \
|
||||
border-1 border-solid border-main text-sm z-index-dropdown-menu \
|
||||
'py-1 bg-default rounded shadow-main transition-transform duration-150 slide-down-animation min-w-70 overflow-y-auto \
|
||||
border border-solid border-border text-sm z-index-dropdown-menu \
|
||||
flex flex-col'
|
||||
}
|
||||
a11yLabel="Notes list options menu"
|
||||
closeMenu={closeDisplayOptionsMenu}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<div className="px-3 my-1 text-xs font-semibold color-text uppercase">Sort by</div>
|
||||
<div className="px-3 my-1 text-xs font-semibold text-text uppercase">Sort by</div>
|
||||
<MenuItem
|
||||
className="py-2"
|
||||
type={MenuItemType.RadioButton}
|
||||
@@ -118,9 +118,9 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
<span>Date modified</span>
|
||||
{sortBy === CollectionSort.UpdatedAt ? (
|
||||
sortReverse ? (
|
||||
<Icon type="arrows-sort-up" className="color-neutral" />
|
||||
<Icon type="arrows-sort-up" className="text-neutral" />
|
||||
) : (
|
||||
<Icon type="arrows-sort-down" className="color-neutral" />
|
||||
<Icon type="arrows-sort-down" className="text-neutral" />
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
@@ -135,9 +135,9 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
<span>Creation date</span>
|
||||
{sortBy === CollectionSort.CreatedAt ? (
|
||||
sortReverse ? (
|
||||
<Icon type="arrows-sort-up" className="color-neutral" />
|
||||
<Icon type="arrows-sort-up" className="text-neutral" />
|
||||
) : (
|
||||
<Icon type="arrows-sort-down" className="color-neutral" />
|
||||
<Icon type="arrows-sort-down" className="text-neutral" />
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
@@ -152,15 +152,15 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
<span>Title</span>
|
||||
{sortBy === CollectionSort.Title ? (
|
||||
sortReverse ? (
|
||||
<Icon type="arrows-sort-up" className="color-neutral" />
|
||||
<Icon type="arrows-sort-up" className="text-neutral" />
|
||||
) : (
|
||||
<Icon type="arrows-sort-down" className="color-neutral" />
|
||||
<Icon type="arrows-sort-down" className="text-neutral" />
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItemSeparator />
|
||||
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
|
||||
<div className="px-3 py-1 text-xs font-semibold text-text uppercase">View</div>
|
||||
{!isFilesSmartView && (
|
||||
<MenuItem
|
||||
type={MenuItemType.SwitchButton}
|
||||
@@ -195,8 +195,8 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
>
|
||||
Show icon
|
||||
</MenuItem>
|
||||
<div className="h-1px my-2 bg-border"></div>
|
||||
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">Other</div>
|
||||
<MenuItemSeparator />
|
||||
<div className="px-3 py-1 text-xs font-semibold text-text uppercase">Other</div>
|
||||
<MenuItem
|
||||
type={MenuItemType.SwitchButton}
|
||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||
|
||||
@@ -3,11 +3,11 @@ import styled from 'styled-components'
|
||||
|
||||
const StyledDisplayOptionsButton = styled(DisclosureButton).attrs(() => ({
|
||||
className:
|
||||
'flex justify-center items-center min-w-8 h-8 bg-color-padding hover:bg-contrast focus:bg-contrast color-neutral border-1 border-solid border-main rounded-full cursor-pointer',
|
||||
'flex justify-center items-center min-w-8 h-8 bg-text-padding hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer',
|
||||
}))<{
|
||||
pressed: boolean
|
||||
$pressed: boolean
|
||||
}>`
|
||||
background-color: ${(props) => (props.pressed ? 'var(--sn-stylekit-contrast-background-color)' : 'transparent')};
|
||||
background-color: ${(props) => (props.$pressed ? 'var(--sn-stylekit-contrast-background-color)' : 'transparent')};
|
||||
`
|
||||
|
||||
export default StyledDisplayOptionsButton
|
||||
|
||||
@@ -10,7 +10,7 @@ type Props = {
|
||||
const ListItemConflictIndicator: FunctionComponent<Props> = ({ item }) => {
|
||||
return item.conflictOf ? (
|
||||
<div className="flex flex-wrap items-center mt-0.5">
|
||||
<div className={'py-1 px-1.5 rounded mr-1 mt-2 bg-danger color-danger-contrast'}>
|
||||
<div className={'py-1 px-1.5 rounded mr-1 mt-2 bg-danger text-danger-contrast'}>
|
||||
<div className="text-xs font-bold text-center">Conflicted Copy</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,30 +14,30 @@ type Props = {
|
||||
|
||||
const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false }) => {
|
||||
return (
|
||||
<div className="flex items-start p-4 pl-0 border-0 border-b-1 border-solid border-main">
|
||||
<div className="flex items-start p-4 pl-0 border-b border-solid border-border">
|
||||
{item.locked && (
|
||||
<span className="flex items-center" title="Editing Disabled">
|
||||
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="sn-icon--small color-info" />
|
||||
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="text-info" size="small" />
|
||||
</span>
|
||||
)}
|
||||
{item.trashed && (
|
||||
<span className="flex items-center ml-1.5" title="Trashed">
|
||||
<Icon ariaLabel="Trashed" type="trash-filled" className="sn-icon--small color-danger" />
|
||||
<Icon ariaLabel="Trashed" type="trash-filled" className="text-danger" size="small" />
|
||||
</span>
|
||||
)}
|
||||
{item.archived && (
|
||||
<span className="flex items-center ml-1.5" title="Archived">
|
||||
<Icon ariaLabel="Archived" type="archive" className="sn-icon--mid color-accessory-tint-3" />
|
||||
<Icon ariaLabel="Archived" type="archive" className="text-accessory-tint-3" size="medium" />
|
||||
</span>
|
||||
)}
|
||||
{item.pinned && (
|
||||
<span className="flex items-center ml-1.5" title="Pinned">
|
||||
<Icon ariaLabel="Pinned" type="pin-filled" className="sn-icon--small color-info" />
|
||||
<Icon ariaLabel="Pinned" type="pin-filled" className="text-info" size="small" />
|
||||
</span>
|
||||
)}
|
||||
{hasFiles && (
|
||||
<span className="flex items-center ml-1.5" title="Files">
|
||||
<Icon ariaLabel="Files" type="attachment-file" className="sn-icon--small color-info" />
|
||||
<Icon ariaLabel="Files" type="attachment-file" className="text-info" size="small" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-xs leading-1.4 mt-1 faded">
|
||||
<div className="text-xs leading-1.4 mt-1 opacity-50">
|
||||
{item.protected && <span>Protected {hideDate ? '' : ' • '}</span>}
|
||||
{!hideDate && showModifiedDate && <span>Modified {item.updatedAtString || 'Now'}</span>}
|
||||
{!hideDate && !showModifiedDate && <span>{item.createdAtString || 'Now'}</span>}
|
||||
|
||||
@@ -16,10 +16,10 @@ const ListItemTags: FunctionComponent<Props> = ({ hideTags, tags }) => {
|
||||
<div className="flex flex-wrap mt-1.5 text-xs gap-2">
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
className="inline-flex items-center py-1 px-1.5 bg-passive-4-opacity-variant color-foreground rounded-0.5"
|
||||
className="inline-flex items-center py-1 px-1.5 bg-passive-4-opacity-variant text-foreground rounded-sm"
|
||||
key={tag.uuid}
|
||||
>
|
||||
<Icon type="hashtag" className="sn-icon--small color-passive-1 mr-1" />
|
||||
<Icon type="hashtag" className="text-passive-1 mr-1" size="small" />
|
||||
<span>{tag.title}</span>
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -45,8 +45,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`content-list-item flex items-stretch w-full cursor-pointer ${
|
||||
selected && 'selected border-0 border-l-2px border-solid border-info'
|
||||
className={`content-list-item flex items-stretch w-full cursor-pointer text-text ${
|
||||
selected && 'selected border-l-2 border-solid border-info'
|
||||
}`}
|
||||
id={item.uuid}
|
||||
onClick={() => {
|
||||
@@ -58,14 +58,14 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
}}
|
||||
>
|
||||
{!hideIcon ? (
|
||||
<div className="flex flex-col items-center justify-between p-4 pr-3 mr-0">
|
||||
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`color-accessory-tint-${tint}`} />
|
||||
<div className="flex flex-col items-center justify-between p-4 pr-4 mr-0">
|
||||
<Icon ariaLabel={`Icon for ${editorName}`} type={icon} className={`text-accessory-tint-${tint}`} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="pr-4" />
|
||||
)}
|
||||
<div className="flex-grow min-w-0 py-4 px-0 border-0 border-b-1 border-solid border-main">
|
||||
<div className="flex items-start justify-between font-semibold text-base leading-1.3 overflow-hidden">
|
||||
<div className="flex-grow min-w-0 py-4 px-0 border-b border-solid border-border">
|
||||
<div className="flex items-start justify-between font-semibold text-base leading-[1.3] overflow-hidden">
|
||||
<div className="break-word mr-2">{item.title}</div>
|
||||
</div>
|
||||
{!hidePreview && !item.hidePreview && !item.protected && (
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ListboxArrow, ListboxButton, ListboxInput, ListboxList, ListboxOption, ListboxPopover } from '@reach/listbox'
|
||||
import { ListboxArrow, ListboxInput, ListboxList, ListboxPopover } from '@reach/listbox'
|
||||
import '@reach/listbox/styles.css'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { FunctionComponent } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { DropdownItem } from './DropdownItem'
|
||||
import StyledListboxButton from './StyledListboxButton'
|
||||
import StyledListboxOption from './StyledListboxOption'
|
||||
|
||||
type DropdownProps = {
|
||||
id: string
|
||||
@@ -25,16 +27,16 @@ const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
|
||||
iconClassName = '',
|
||||
}) => (
|
||||
<>
|
||||
<div className="sn-dropdown-button-label">
|
||||
<div className="flex items-center">
|
||||
{icon ? (
|
||||
<div className="flex mr-2">
|
||||
<Icon type={icon} className={`sn-icon--small ${iconClassName}`} />
|
||||
<Icon type={icon} className={iconClassName} size="small" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="dropdown-selected-label">{label}</div>
|
||||
</div>
|
||||
<ListboxArrow className={`sn-dropdown-arrow ${isExpanded ? 'sn-dropdown-arrow-flipped' : ''}`}>
|
||||
<Icon type="menu-arrow-down" className="sn-icon--small color-passive-1" />
|
||||
<ListboxArrow className={`flex ${isExpanded ? 'rotate-180' : ''}`}>
|
||||
<Icon type="menu-arrow-down" className="text-passive-1" size="small" />
|
||||
</ListboxArrow>
|
||||
</>
|
||||
)
|
||||
@@ -52,8 +54,7 @@ const Dropdown: FunctionComponent<DropdownProps> = ({ id, label, items, value, o
|
||||
<>
|
||||
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
|
||||
<ListboxInput value={value} onChange={handleChange} aria-labelledby={labelId} disabled={disabled}>
|
||||
<ListboxButton
|
||||
className="sn-dropdown-button"
|
||||
<StyledListboxButton
|
||||
children={({ value, label, isExpanded }) => {
|
||||
const current = items.find((item) => item.value === value)
|
||||
const icon = current ? current?.icon : null
|
||||
@@ -71,20 +72,14 @@ const Dropdown: FunctionComponent<DropdownProps> = ({ id, label, items, value, o
|
||||
<div className="sn-component">
|
||||
<ListboxList>
|
||||
{items.map((item) => (
|
||||
<ListboxOption
|
||||
key={item.value}
|
||||
className="sn-dropdown-item"
|
||||
value={item.value}
|
||||
label={item.label}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<StyledListboxOption key={item.value} value={item.value} label={item.label} disabled={item.disabled}>
|
||||
{item.icon ? (
|
||||
<div className="flex mr-3">
|
||||
<Icon type={item.icon} className={`sn-icon--small ${item.iconClassName ?? ''}`} />
|
||||
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-input">{item.label}</div>
|
||||
</ListboxOption>
|
||||
</StyledListboxOption>
|
||||
))}
|
||||
</ListboxList>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ListboxButton } from '@reach/listbox'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledListboxButton = styled(ListboxButton)`
|
||||
&[data-reach-listbox-button] {
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--sn-stylekit-border-color);
|
||||
color: var(--sn-stylekit-contrast-foreground-color);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
min-width: 13.75rem;
|
||||
padding-bottom: 0.375rem;
|
||||
padding-left: 0.875rem;
|
||||
padding-right: 0.875rem;
|
||||
padding-top: 0.375rem;
|
||||
width: fit-content;
|
||||
}
|
||||
`
|
||||
|
||||
export default StyledListboxButton
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ListboxOption } from '@reach/listbox'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledListboxOption = styled(ListboxOption)`
|
||||
&[data-reach-listbox-option] {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--sn-stylekit-contrast-foreground-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 0.875rem;
|
||||
padding-bottom: 0.375rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
padding-top: 0.375rem;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&[data-current-selected] {
|
||||
color: var(--sn-stylekit-info-color);
|
||||
background-color: var(--sn-stylekit-info-backdrop-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--sn-stylekit-info-backdrop-color);
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default StyledListboxOption
|
||||
@@ -86,7 +86,7 @@ const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, s
|
||||
return (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className="sn-dropdown min-w-60 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
|
||||
className="bg-default rounded shadow-main min-w-60 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
|
||||
style={{
|
||||
...contextMenuStyle,
|
||||
maxHeight: contextMenuMaxHeight,
|
||||
|
||||
@@ -6,6 +6,7 @@ import Switch from '@/Components/Switch/Switch'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
|
||||
type Props = {
|
||||
closeMenu: () => void
|
||||
@@ -64,35 +65,47 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onPreview}>
|
||||
<Icon type="file" className="mr-2 color-neutral" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={onPreview}
|
||||
>
|
||||
<Icon type="file" className="mr-2 text-neutral" />
|
||||
Preview file
|
||||
</button>
|
||||
{selectedFiles.length === 1 && (
|
||||
<>
|
||||
{isFileAttachedToNote ? (
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onDetach}>
|
||||
<Icon type="link-off" className="mr-2 color-neutral" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={onDetach}
|
||||
>
|
||||
<Icon type="link-off" className="mr-2 text-neutral" />
|
||||
Detach from note
|
||||
</button>
|
||||
) : shouldShowAttachOption ? (
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item focus:bg-info-backdrop" onClick={onAttach}>
|
||||
<Icon type="link" className="mr-2 color-neutral" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={onAttach}
|
||||
>
|
||||
<Icon type="link" className="mr-2 text-neutral" />
|
||||
Attach to note
|
||||
</button>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
<div className="min-h-1px my-1 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
className="sn-dropdown-item justify-between focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm justify-between"
|
||||
onClick={() => {
|
||||
void filesController.setProtectionForFiles(!hasProtectedFiles, selectionController.selectedFiles)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className="mr-2 color-neutral" />
|
||||
<Icon type="password" className="mr-2 text-neutral" />
|
||||
Password protection
|
||||
</span>
|
||||
<Switch
|
||||
@@ -101,41 +114,41 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
checked={hasProtectedFiles}
|
||||
/>
|
||||
</button>
|
||||
<div className="min-h-1px my-1 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={() => {
|
||||
void filesController.downloadFiles(selectionController.selectedFiles)
|
||||
}}
|
||||
>
|
||||
<Icon type="download" className="mr-2 color-neutral" />
|
||||
<Icon type="download" className="mr-2 text-neutral" />
|
||||
Download
|
||||
</button>
|
||||
{shouldShowRenameOption && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={() => {
|
||||
renameToggleCallback?.(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="mr-2 color-neutral" />
|
||||
<Icon type="pencil" className="mr-2 text-neutral" />
|
||||
Rename
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
onClick={() => {
|
||||
void filesController.deleteFilesPermanently(selectionController.selectedFiles)
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="mr-2 color-danger" />
|
||||
<span className="color-danger">Delete permanently</span>
|
||||
<Icon type="trash" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</button>
|
||||
{selectedFiles.length === 1 && (
|
||||
<div className="px-3 pt-1.5 pb-0.5 text-xs color-neutral font-medium">
|
||||
<div className="px-3 pt-1.5 pb-0.5 text-xs text-neutral font-medium">
|
||||
<div>
|
||||
<span className="font-semibold">File ID:</span> {selectedFiles[0].uuid}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="sn-icon-button border-contrast"
|
||||
className="flex justify-center items-center min-w-8 h-8 bg-text-padding hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer"
|
||||
>
|
||||
<VisuallyHidden>Actions</VisuallyHidden>
|
||||
<Icon type="more" className="block" />
|
||||
@@ -69,7 +69,9 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
|
||||
className={`${
|
||||
open ? 'flex' : 'hidden'
|
||||
} flex-col min-w-80 max-h-120 max-w-xs py-2 fixed bg-default rounded shadow-main transition-transform duration-150 slide-down-animation overflow-y-auto`}
|
||||
onBlur={closeOnBlur}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { WebApplication } from '@/Application/Application'
|
||||
import { concatenateUint8Arrays } from '@/Utils'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import FilePreviewError from './FilePreviewError'
|
||||
import { isFileTypePreviewable } from './isFilePreviewable'
|
||||
import PreviewComponent from './PreviewComponent'
|
||||
@@ -59,7 +60,7 @@ const FilePreview = ({ file, application }: Props) => {
|
||||
return isDownloading ? (
|
||||
<div className="flex flex-col justify-center items-center flex-grow">
|
||||
<div className="flex items-center">
|
||||
<div className="sk-spinner w-5 h-5 spinner-info mr-3"></div>
|
||||
<Spinner className="w-5 h-5 mr-3" />
|
||||
<div className="text-base font-semibold">{downloadProgress}%</div>
|
||||
</div>
|
||||
<span className="mt-3">Loading file...</span>
|
||||
|
||||
@@ -17,12 +17,12 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
|
||||
<div className="font-bold text-base mb-2">This file can't be previewed.</div>
|
||||
{isFilePreviewable ? (
|
||||
<>
|
||||
<div className="text-sm text-center color-passive-0 mb-4 max-w-35ch">
|
||||
<div className="text-sm text-center text-passive-0 mb-4 max-w-35ch">
|
||||
There was an error loading the file. Try again, or download the file and open it using another application.
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="primary"
|
||||
primary
|
||||
className="mr-3"
|
||||
onClick={() => {
|
||||
tryAgainCallback()
|
||||
@@ -31,7 +31,6 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
|
||||
Try again
|
||||
</Button>
|
||||
<Button
|
||||
variant="normal"
|
||||
onClick={() => {
|
||||
filesController.downloadFile(file).catch(console.error)
|
||||
}}
|
||||
@@ -42,11 +41,11 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm text-center color-passive-0 mb-4 max-w-35ch">
|
||||
<div className="text-sm text-center text-passive-0 mb-4 max-w-35ch">
|
||||
To view this file, download it and open it using another application.
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
primary
|
||||
onClick={() => {
|
||||
filesController.downloadFile(file).catch(console.error)
|
||||
}}
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
|
||||
const FilePreviewInfoPanel: FunctionComponent<Props> = ({ file }) => {
|
||||
return (
|
||||
<div className="flex flex-col min-w-70 p-4 border-0 border-l-1px border-solid border-main">
|
||||
<div className="flex flex-col min-w-70 p-4 border-0 border-l border-solid border-border">
|
||||
<div className="flex items-center mb-4">
|
||||
<Icon type="info" className="mr-2" />
|
||||
<div className="font-semibold">File information</div>
|
||||
|
||||
@@ -78,16 +78,10 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
>
|
||||
<DialogContent
|
||||
aria-label="File preview modal"
|
||||
className="flex flex-col rounded shadow-overlay"
|
||||
style={{
|
||||
width: '90%',
|
||||
maxWidth: '90%',
|
||||
minHeight: '90%',
|
||||
background: 'var(--modal-background-color)',
|
||||
}}
|
||||
className="flex flex-col rounded shadow-main p-0 min-w-[90%] min-h-[90%] bg-[color:var(--modal-background-color)] "
|
||||
>
|
||||
<div
|
||||
className="flex flex-shrink-0 justify-between items-center min-h-6 px-4 py-3 border-0 border-b-1 border-solid border-main focus:shadow-none"
|
||||
className="flex flex-shrink-0 justify-between items-center min-h-6 px-4 py-3 border-0 border-b border-solid border-border focus:shadow-none"
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
onKeyDown={keyDownHandler}
|
||||
>
|
||||
@@ -97,10 +91,10 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className="flex p-1.5 mr-4 bg-transparent hover:bg-contrast border-solid border-main border-1 cursor-pointer rounded"
|
||||
className="flex p-1.5 mr-4 bg-transparent hover:bg-contrast border-solid border-border border cursor-pointer rounded"
|
||||
onClick={() => setShowFileInfoPanel((show) => !show)}
|
||||
>
|
||||
<Icon type="info" className="color-neutral" />
|
||||
<Icon type="info" className="text-neutral" />
|
||||
</button>
|
||||
<button
|
||||
ref={closeButtonRef}
|
||||
@@ -108,7 +102,7 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
aria-label="Close modal"
|
||||
className="flex p-1 bg-transparent hover:bg-contrast border-0 cursor-pointer rounded"
|
||||
>
|
||||
<Icon type="close" className="color-neutral" />
|
||||
<Icon type="close" className="text-neutral" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ const ImagePreview: FunctionComponent<Props> = ({ objectUrl }) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center absolute left-1/2 -translate-x-1/2 bottom-6 py-1 px-3 bg-default border-1 border-solid border-main rounded">
|
||||
<div className="flex items-center absolute left-1/2 -translate-x-1/2 bottom-6 py-1 px-3 bg-default border border-solid border-border rounded">
|
||||
<span className="mr-1.5">Zoom:</span>
|
||||
<IconButton
|
||||
className="hover:bg-contrast p-1 rounded"
|
||||
|
||||
@@ -31,12 +31,15 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
return (
|
||||
<div className="sn-component section editor" aria-label="File">
|
||||
<div className="flex flex-col">
|
||||
<div className="content-title-bar section-title-bar w-full" id="file-title-bar">
|
||||
<div
|
||||
className="content-title-bar section-title-bar z-editor-title-bar section-title-bar w-full"
|
||||
id="file-title-bar"
|
||||
>
|
||||
<div className="flex items-center justify-between h-8">
|
||||
<div className="flex-grow">
|
||||
<form onSubmit={onFormSubmit} className="title overflow-auto">
|
||||
<input
|
||||
className="input"
|
||||
className="input text-lg"
|
||||
id={ElementIds.FileTitleEditor}
|
||||
onChange={onTitleChange}
|
||||
onFocus={(event) => {
|
||||
|
||||
@@ -342,9 +342,12 @@ class Footer extends PureComponent<Props, State> {
|
||||
override render() {
|
||||
return (
|
||||
<div className="sn-component">
|
||||
<div id="footer-bar" className="sk-app-bar no-edges no-bottom-edge">
|
||||
<div className="left">
|
||||
<div className="sk-app-bar-item ml-0">
|
||||
<div
|
||||
id="footer-bar"
|
||||
className="flex justify-between items-center w-full h-6 px-3 bg-contrast text-text z-footer-bar border-t border-border select-none"
|
||||
>
|
||||
<div className="left flex h-full">
|
||||
<div className="sk-app-bar-item z-footer-bar-item relative select-none ml-0">
|
||||
<div
|
||||
onClick={this.accountMenuClickHandler}
|
||||
className={
|
||||
@@ -352,8 +355,12 @@ class Footer extends PureComponent<Props, State> {
|
||||
' w-8 h-full flex items-center justify-center cursor-pointer rounded-full'
|
||||
}
|
||||
>
|
||||
<div className={this.state.hasError ? 'danger' : (this.user ? 'info' : 'neutral') + ' w-5 h-5'}>
|
||||
<Icon type="account-circle" className="hover:color-info w-5 h-5 max-h-5" />
|
||||
<div
|
||||
className={
|
||||
this.state.hasError ? 'text-danger' : (this.user ? 'text-info' : 'text-neutral') + ' w-5 h-5'
|
||||
}
|
||||
>
|
||||
<Icon type="account-circle" className="hover:text-info max-h-5" />
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showAccountMenu && (
|
||||
@@ -365,7 +372,7 @@ class Footer extends PureComponent<Props, State> {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="sk-app-bar-item ml-0-important">
|
||||
<div className="sk-app-bar-item z-footer-bar-item relative select-none ml-0-important">
|
||||
<div
|
||||
onClick={this.quickSettingsClickHandler}
|
||||
className="w-8 h-full flex items-center justify-center cursor-pointer"
|
||||
@@ -373,7 +380,7 @@ class Footer extends PureComponent<Props, State> {
|
||||
<div className="h-5">
|
||||
<Icon
|
||||
type="tune"
|
||||
className={(this.state.showQuickSettingsMenu ? 'color-info' : '') + ' rounded hover:color-info'}
|
||||
className={(this.state.showQuickSettingsMenu ? 'text-info' : '') + ' rounded hover:text-info'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -387,9 +394,8 @@ class Footer extends PureComponent<Props, State> {
|
||||
</div>
|
||||
{this.state.showBetaWarning && (
|
||||
<Fragment>
|
||||
<div className="sk-app-bar-item border" />
|
||||
<div className="sk-app-bar-item">
|
||||
<a onClick={this.betaMessageClickHandler} className="no-decoration sk-label title">
|
||||
<div className="flex items-center z-footer-bar-item pl-3 ml-3 relative select-none border-l border-solid border-border">
|
||||
<a onClick={this.betaMessageClickHandler} className="no-decoration text-xs font-bold title">
|
||||
You are using a beta version of the app
|
||||
</a>
|
||||
</div>
|
||||
@@ -398,28 +404,32 @@ class Footer extends PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="center">
|
||||
{this.state.arbitraryStatusMessage && (
|
||||
<div className="sk-app-bar-item">
|
||||
<div className="sk-app-bar-item-column">
|
||||
<span className="neutral sk-label">{this.state.arbitraryStatusMessage}</span>
|
||||
</div>
|
||||
<div className="flex items-center z-footer-bar-item relative select-none text-xs text-neutral font-bold">
|
||||
{this.state.arbitraryStatusMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="right flex h-full">
|
||||
{this.state.dataUpgradeAvailable && (
|
||||
<div onClick={this.securityUpdateClickHandler} className="sk-app-bar-item">
|
||||
<span className="success sk-label">Encryption upgrade available.</span>
|
||||
<div
|
||||
onClick={this.securityUpdateClickHandler}
|
||||
className="flex items-center text-xs text-success font-bold z-footer-bar-item relative select-none"
|
||||
>
|
||||
Encryption upgrade available.
|
||||
</div>
|
||||
)}
|
||||
{this.state.newUpdateAvailable && (
|
||||
<div onClick={this.newUpdateClickHandler} className="sk-app-bar-item">
|
||||
<span className="info sk-label">New update available.</span>
|
||||
<div
|
||||
onClick={this.newUpdateClickHandler}
|
||||
className="flex items-center ml-3 text-xs text-info font-bold z-footer-bar-item relative select-none"
|
||||
>
|
||||
New update available.
|
||||
</div>
|
||||
)}
|
||||
{(this.state.outOfSync || this.state.showSyncResolution) && (
|
||||
<div className="sk-app-bar-item">
|
||||
<div className="flex items-center ml-3 z-footer-bar-item relative select-none">
|
||||
{this.state.outOfSync && (
|
||||
<div onClick={this.syncResolutionClickHandler} className="sk-label warning">
|
||||
<div onClick={this.syncResolutionClickHandler} className="font-bold text-xs text-warning">
|
||||
Potentially Out of Sync
|
||||
</div>
|
||||
)}
|
||||
@@ -429,22 +439,19 @@ class Footer extends PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
{this.state.offline && (
|
||||
<div className="sk-app-bar-item">
|
||||
<div className="sk-label">Offline</div>
|
||||
<div className="flex items-center ml-3 text-xs font-bold z-footer-bar-item relative select-none">
|
||||
Offline
|
||||
</div>
|
||||
)}
|
||||
{this.state.hasPasscode && (
|
||||
<Fragment>
|
||||
<div className="sk-app-bar-item border" />
|
||||
<div
|
||||
id="lock-item"
|
||||
onClick={this.lockClickHandler}
|
||||
title="Locks application and wipes unencrypted data from memory."
|
||||
className="sk-app-bar-item pl-1 hover:color-info"
|
||||
>
|
||||
<Icon type="lock-filled" />
|
||||
</div>
|
||||
</Fragment>
|
||||
<div
|
||||
id="lock-item"
|
||||
onClick={this.lockClickHandler}
|
||||
title="Locks application and wipes unencrypted data from memory."
|
||||
className="flex items-center z-footer-bar-item relative select-none pl-2 ml-3 hover:text-info border-l border-solid border-border cursor-pointer"
|
||||
>
|
||||
<Icon type="lock-filled" size="custom" className="w-4.5 h-4.5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { FunctionComponent, useMemo } from 'react'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
import {
|
||||
@@ -187,16 +187,32 @@ type Props = {
|
||||
type: IconType
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
size?: 'small' | 'medium' | 'normal' | 'custom'
|
||||
}
|
||||
|
||||
const Icon: FunctionComponent<Props> = ({ type, className = '', ariaLabel }) => {
|
||||
const Icon: FunctionComponent<Props> = ({ type, className = '', ariaLabel, size = 'normal' }) => {
|
||||
const IconComponent = ICONS[type as keyof typeof ICONS]
|
||||
|
||||
const dimensions = useMemo(() => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 'w-3.5 h-3.5'
|
||||
case 'medium':
|
||||
return 'w-4 h-4'
|
||||
case 'custom':
|
||||
return ''
|
||||
default:
|
||||
return 'w-5 h-5'
|
||||
}
|
||||
}, [size])
|
||||
|
||||
if (!IconComponent) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
className={`sn-icon ${className}`}
|
||||
className={`${dimensions} fill-current ${className}`}
|
||||
role="img"
|
||||
{...(ariaLabel ? { 'aria-label': ariaLabel } : { 'aria-hidden': true })}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
type Props = {
|
||||
style: 'neutral' | 'info' | 'danger'
|
||||
}
|
||||
|
||||
const baseClassNames = 'border border-solid w-3 h-3 p-0 rounded-full flex-shrink-0'
|
||||
|
||||
const IndicatorCircle = ({ style }: Props) => {
|
||||
switch (style) {
|
||||
case 'neutral':
|
||||
return <div className={`${baseClassNames} bg-neutral border-neutral`} />
|
||||
case 'info':
|
||||
return <div className={`${baseClassNames} bg-info border-info`} />
|
||||
case 'danger':
|
||||
return <div className={`${baseClassNames} bg-danger border-danger`} />
|
||||
}
|
||||
}
|
||||
|
||||
export default IndicatorCircle
|
||||
@@ -3,10 +3,10 @@ import { DecoratedInputProps } from './DecoratedInputProps'
|
||||
|
||||
const getClassNames = (hasLeftDecorations: boolean, hasRightDecorations: boolean) => {
|
||||
return {
|
||||
container: `flex items-stretch position-relative bg-default border-1 border-solid border-main rounded focus-within:ring-info overflow-hidden ${
|
||||
container: `flex items-stretch position-relative bg-default border border-solid border-border rounded focus-within:ring-2 focus-within:ring-info overflow-hidden text-sm ${
|
||||
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
|
||||
}`,
|
||||
input: `w-full border-0 focus:shadow-none bg-transparent color-text ${
|
||||
input: `w-full border-0 focus:shadow-none focus:outline-none focus:ring-none bg-transparent text-text ${
|
||||
!hasLeftDecorations && hasRightDecorations ? 'pl-2' : ''
|
||||
} ${hasRightDecorations ? 'pr-2' : ''}`,
|
||||
disabled: 'bg-passive-5 cursor-not-allowed',
|
||||
@@ -21,6 +21,7 @@ const DecoratedInput = forwardRef(
|
||||
{
|
||||
type = 'text',
|
||||
className = '',
|
||||
id = '',
|
||||
disabled = false,
|
||||
left,
|
||||
right,
|
||||
@@ -49,6 +50,7 @@ const DecoratedInput = forwardRef(
|
||||
|
||||
<input
|
||||
type={type}
|
||||
id={id}
|
||||
className={`${classNames.input} ${disabled ? classNames.disabled : ''}`}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react'
|
||||
export type DecoratedInputProps = {
|
||||
type?: 'text' | 'email' | 'password'
|
||||
className?: string
|
||||
id?: string
|
||||
disabled?: boolean
|
||||
left?: ReactNode[]
|
||||
right?: ReactNode[]
|
||||
|
||||
@@ -8,9 +8,9 @@ const Toggle: FunctionComponent<{
|
||||
setIsToggled: Dispatch<SetStateAction<boolean>>
|
||||
}> = ({ isToggled, setIsToggled }) => (
|
||||
<IconButton
|
||||
className="w-5 h-5 p-0 justify-center sk-circle hover:bg-passive-4 color-neutral"
|
||||
className="w-5 h-5 p-0 justify-center rounded-full hover:bg-passive-4 text-neutral"
|
||||
icon={isToggled ? 'eye-off' : 'eye'}
|
||||
iconClassName="sn-icon--small"
|
||||
iconClassName="w-3.5 h-3.5"
|
||||
title="Show/hide password"
|
||||
onClick={() => setIsToggled((isToggled) => !isToggled)}
|
||||
focusable={true}
|
||||
|
||||
@@ -33,14 +33,14 @@ const FloatingLabelInput = forwardRef(
|
||||
|
||||
const BASE_CLASSNAME = 'relative bg-default'
|
||||
|
||||
const LABEL_CLASSNAME = `hidden absolute ${!focused ? 'color-neutral' : 'color-info'} ${
|
||||
focused || value ? 'flex top-0 left-2 pt-1.5 px-1' : ''
|
||||
} ${isInvalid ? 'color-danger' : ''} ${labelClassName}`
|
||||
const LABEL_CLASSNAME = `absolute ${!focused ? 'text-neutral' : 'text-info'} ${
|
||||
focused || value ? 'flex top-0 left-2 pt-1.5 px-1' : 'hidden'
|
||||
} ${isInvalid ? 'text-danger' : ''} ${labelClassName}`
|
||||
|
||||
const INPUT_CLASSNAME = `w-full h-full ${
|
||||
focused || value ? 'pt-6 pb-2' : 'py-2.5'
|
||||
} px-3 text-input border-1 border-solid border-main rounded placeholder-medium text-input focus:ring-info ${
|
||||
isInvalid ? 'border-danger placeholder-dark-red' : ''
|
||||
} px-3 text-sm border border-solid border-border rounded placeholder:font-medium focus:ring-info ${
|
||||
isInvalid ? 'border-danger placeholder:text-danger' : ''
|
||||
} ${inputClassName}`
|
||||
|
||||
const handleFocus = () => setFocused(true)
|
||||
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
|
||||
const Input: FunctionComponent<Props> = ({ className = '', disabled = false, text }) => {
|
||||
const base = 'rounded py-1.5 px-3 text-input my-1 h-8 bg-contrast'
|
||||
const stateClasses = disabled ? 'no-border' : 'border-solid border-1 border-main'
|
||||
const stateClasses = disabled ? 'no-border' : 'border-solid border border-border'
|
||||
const classes = `${base} ${stateClasses} ${className}`
|
||||
return <input type="text" className={classes} disabled={disabled} value={text} />
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const Menu: FunctionComponent<MenuProps> = ({
|
||||
|
||||
return (
|
||||
<menu
|
||||
className={`m-0 pl-0 list-style-none focus:shadow-none ${className}`}
|
||||
className={`m-0 pl-0 list-none focus:shadow-none ${className}`}
|
||||
onKeyDown={handleKeyDown}
|
||||
ref={menuElementRef}
|
||||
style={style}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SwitchProps } from '@/Components/Switch/SwitchProps'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { MenuItemType } from './MenuItemType'
|
||||
import RadioIndicator from '../RadioIndicator/RadioIndicator'
|
||||
|
||||
type MenuItemProps = {
|
||||
type: MenuItemType
|
||||
@@ -36,10 +37,10 @@ const MenuItem = forwardRef(
|
||||
ref: Ref<HTMLButtonElement>,
|
||||
) => {
|
||||
return type === MenuItemType.SwitchButton && typeof onChange === 'function' ? (
|
||||
<li className="list-style-none" role="none">
|
||||
<li className="list-none" role="none">
|
||||
<button
|
||||
ref={ref}
|
||||
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
|
||||
className="flex items-center justify-between border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
onChange(!checked)
|
||||
}}
|
||||
@@ -53,19 +54,19 @@ const MenuItem = forwardRef(
|
||||
</button>
|
||||
</li>
|
||||
) : (
|
||||
<li className="list-style-none" role="none">
|
||||
<li className="list-none" role="none">
|
||||
<button
|
||||
ref={ref}
|
||||
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
|
||||
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
className={`sn-dropdown-item focus:bg-info-backdrop focus:shadow-none ${className}`}
|
||||
className={`flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item ${className}`}
|
||||
onClick={onClick}
|
||||
onBlur={onBlur}
|
||||
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
||||
>
|
||||
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
|
||||
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
|
||||
<div className={`pseudo-radio-btn ${checked ? 'pseudo-radio-btn--checked' : ''} flex-shrink-0`}></div>
|
||||
<RadioIndicator checked={checked} className="flex-shrink-0" />
|
||||
) : null}
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
const MenuItemSeparator: FunctionComponent = () => (
|
||||
<li className="list-style-none" role="none">
|
||||
<div role="separator" className="h-1px my-2 bg-border" />
|
||||
<li className="list-none" role="none">
|
||||
<div role="separator" className="h-[1px] my-2 bg-border" />
|
||||
</li>
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const MultipleSelectedFiles = ({
|
||||
return (
|
||||
<div className="flex flex-col h-full items-center">
|
||||
<div className="flex items-center justify-between p-4 w-full">
|
||||
<h1 className="sk-h1 font-bold m-0">{count} selected files</h1>
|
||||
<h1 className="text-lg font-bold m-0">{count} selected files</h1>
|
||||
<div className="flex">
|
||||
<div className="mr-3">
|
||||
<AttachedFilesButton
|
||||
@@ -58,7 +58,7 @@ const MultipleSelectedFiles = ({
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
|
||||
<IlNotesIcon className="block" />
|
||||
<h2 className="text-lg m-0 text-center mt-4">{count} selected files</h2>
|
||||
<h2 className="font-bold text-lg m-0 text-center mt-4">{count} selected files</h2>
|
||||
<p className="text-sm mt-2 text-center max-w-60">Actions will be performed on all selected files.</p>
|
||||
<Button className="mt-2.5" onClick={cancelMultipleSelection}>
|
||||
Cancel multiple selection
|
||||
|
||||
@@ -47,7 +47,7 @@ const MultipleSelectedNotes = ({
|
||||
return (
|
||||
<div className="flex flex-col h-full items-center">
|
||||
<div className="flex items-center justify-between p-4 w-full">
|
||||
<h1 className="sk-h1 font-bold m-0">{count} selected notes</h1>
|
||||
<h1 className="text-lg font-bold m-0">{count} selected notes</h1>
|
||||
<div className="flex">
|
||||
<div className="mr-3">
|
||||
<AttachedFilesButton
|
||||
@@ -74,7 +74,7 @@ const MultipleSelectedNotes = ({
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
|
||||
<IlNotesIcon className="block" />
|
||||
<h2 className="text-lg m-0 text-center mt-4">{count} selected notes</h2>
|
||||
<h2 className="font-bold text-lg m-0 text-center mt-4">{count} selected notes</h2>
|
||||
<p className="text-sm mt-2 text-center max-w-60">Actions will be performed on all selected notes.</p>
|
||||
<Button className="mt-2.5" onClick={cancelMultipleSelection}>
|
||||
Cancel multiple selection
|
||||
|
||||
@@ -52,8 +52,8 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
||||
<div id="navigation-content" className="content">
|
||||
<div className="section-title-bar">
|
||||
<div className="section-title-bar-header">
|
||||
<div className="sk-h3 title">
|
||||
<span className="sk-bold">Views</span>
|
||||
<div className="text-sm title">
|
||||
<span className="font-bold">Views</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuCont
|
||||
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { MouseEventHandler, useCallback } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
accountMenuController: AccountMenuController
|
||||
@@ -23,18 +24,18 @@ const NoAccountWarningContent = ({ accountMenuController, noAccountWarningContro
|
||||
}, [noAccountWarningController])
|
||||
|
||||
return (
|
||||
<div className="mt-4 p-4 rounded-md shadow-sm grid grid-template-cols-1fr">
|
||||
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
|
||||
<p className="m-0 mt-1 col-start-1 col-end-3">Sign in or register to back up your notes.</p>
|
||||
<button className="sn-button small info mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
|
||||
<div className="mt-4 p-4 rounded-md shadow grid grid-cols-1">
|
||||
<h1 className="sk-h3 m-0 font-semibold text-sm">Data not backed up</h1>
|
||||
<p className="m-0 mt-1 col-start-1 col-end-3 text-sm">Sign in or register to back up your notes.</p>
|
||||
<Button primary small className="mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
|
||||
Open Account menu
|
||||
</button>
|
||||
</Button>
|
||||
<button
|
||||
onClick={hideWarning}
|
||||
title="Ignore warning"
|
||||
aria-label="Ignore warning"
|
||||
style={{ height: '20px' }}
|
||||
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
|
||||
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 text-neutral hover:text-info"
|
||||
>
|
||||
<Icon type="close" className="block" />
|
||||
</button>
|
||||
|
||||
@@ -116,7 +116,7 @@ const NoteTag = ({ viewControllerManager, tag }: Props) => {
|
||||
return (
|
||||
<button
|
||||
ref={tagRef}
|
||||
className="sn-tag pl-1 pr-2 mr-2"
|
||||
className="h-6 bg-contrast border-0 rounded text-xs text-text flex items-center mt-2 cursor-pointer hover:bg-secondary-contrast focus:bg-secondary-contrast py-2 pl-1 pr-2 mr-2"
|
||||
onClick={onTagClick}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={onFocus}
|
||||
@@ -124,9 +124,9 @@ const NoteTag = ({ viewControllerManager, tag }: Props) => {
|
||||
tabIndex={getTabIndex()}
|
||||
title={longTitle}
|
||||
>
|
||||
<Icon type="hashtag" className="sn-icon--small color-info mr-1" />
|
||||
<Icon type="hashtag" className="text-info mr-1" size="small" />
|
||||
<span className="whitespace-nowrap overflow-hidden overflow-ellipsis max-w-290px">
|
||||
{prefixTitle && <span className="color-passive-1">{prefixTitle}</span>}
|
||||
{prefixTitle && <span className="text-passive-1">{prefixTitle}</span>}
|
||||
{title}
|
||||
</span>
|
||||
{showDeleteButton && (
|
||||
@@ -138,7 +138,7 @@ const NoteTag = ({ viewControllerManager, tag }: Props) => {
|
||||
onClick={onDeleteTagClick}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Icon type="close" className="sn-icon--small color-neutral hover:color-info" />
|
||||
<Icon type="close" className="text-neutral hover:text-info" size="small" />
|
||||
</a>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -17,12 +17,12 @@ const EditingDisabledBanner: FunctionComponent<Props> = ({
|
||||
lockText,
|
||||
}) => {
|
||||
const background = showLockedIcon ? 'bg-warning-faded' : 'bg-info-faded'
|
||||
const iconColor = showLockedIcon ? 'color-accessory-tint-3' : 'color-accessory-tint-1'
|
||||
const textColor = showLockedIcon ? 'color-warning' : 'color-accessory-tint-1'
|
||||
const iconColor = showLockedIcon ? 'text-accessory-tint-3' : 'text-accessory-tint-1'
|
||||
const textColor = showLockedIcon ? 'text-warning' : 'text-accessory-tint-1'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center relative ${background} px-3.5 py-2 cursor-pointer`}
|
||||
className={`flex items-center relative ${background} px-3.5 py-2 cursor-pointer text-sm`}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOver={onMouseOver}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
import { reloadFont } from './FontFunctions'
|
||||
import { NoteViewProps } from './NoteViewProps'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||
|
||||
const MINIMUM_STATUS_DURATION = 400
|
||||
const TEXTAREA_DEBOUNCE = 100
|
||||
@@ -907,12 +908,15 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
)}
|
||||
|
||||
{this.note && (
|
||||
<div id="editor-title-bar" className="content-title-bar section-title-bar w-full">
|
||||
<div
|
||||
id="editor-title-bar"
|
||||
className="content-title-bar section-title-bar z-editor-title-bar section-title-bar w-full"
|
||||
>
|
||||
<div className="flex items-center justify-between h-8">
|
||||
<div className={(this.state.noteLocked ? 'locked' : '') + ' flex-grow'}>
|
||||
<div className="title overflow-auto">
|
||||
<input
|
||||
className="input"
|
||||
className="input text-lg"
|
||||
disabled={this.state.noteLocked}
|
||||
id={ElementIds.NoteTitleEditor}
|
||||
onChange={this.onTitleChange}
|
||||
@@ -931,14 +935,14 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
<div id="save-status">
|
||||
<div
|
||||
className={
|
||||
(this.state.syncTakingTooLong ? 'warning sk-bold ' : '') +
|
||||
(this.state.saveError ? 'danger sk-bold ' : '') +
|
||||
' message'
|
||||
(this.state.syncTakingTooLong ? 'text-warning font-bold ' : '') +
|
||||
(this.state.saveError ? 'text-danger font-bold ' : '') +
|
||||
'text-xs message'
|
||||
}
|
||||
>
|
||||
{this.state.noteStatus?.message}
|
||||
</div>
|
||||
{this.state.noteStatus?.desc && <div className="desc">{this.state.noteStatus.desc}</div>}
|
||||
{this.state.noteStatus?.desc && <div className="text-xs desc">{this.state.noteStatus.desc}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mr-3">
|
||||
@@ -980,7 +984,11 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div id={ElementIds.EditorContent} className={ElementIds.EditorContent} ref={this.editorContentRef}>
|
||||
<div
|
||||
id={ElementIds.EditorContent}
|
||||
className={`${ElementIds.EditorContent} z-editor-content`}
|
||||
ref={this.editorContentRef}
|
||||
>
|
||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
||||
<PanelResizer
|
||||
minWidth={300}
|
||||
@@ -1039,8 +1047,11 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
|
||||
<div id="editor-pane-component-stack">
|
||||
{this.state.availableStackComponents.length > 0 && (
|
||||
<div id="component-stack-menu-bar" className="sk-app-bar no-edges">
|
||||
<div className="left">
|
||||
<div
|
||||
id="component-stack-menu-bar"
|
||||
className="flex justify-between items-center w-full h-6 px-2 py-0 bg-contrast text-text border-t border-solid border-border"
|
||||
>
|
||||
<div className="flex h-full">
|
||||
{this.state.availableStackComponents.map((component) => {
|
||||
return (
|
||||
<div
|
||||
@@ -1048,19 +1059,16 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
onClick={() => {
|
||||
this.toggleStackComponent(component).catch(console.error)
|
||||
}}
|
||||
className="sk-app-bar-item"
|
||||
className="flex justify-center items-center flex-grow [&:not(:first-child)]:ml-3 cursor-pointer"
|
||||
>
|
||||
<div className="sk-app-bar-item-column">
|
||||
<div
|
||||
className={
|
||||
(this.stackComponentExpanded(component) && component.active ? 'info ' : '') +
|
||||
(!this.stackComponentExpanded(component) ? 'neutral ' : '') +
|
||||
' sk-circle small'
|
||||
}
|
||||
/>
|
||||
<div className="flex items-center h-full [&:not(:first-child)]:ml-2">
|
||||
{this.stackComponentExpanded(component) && component.active && (
|
||||
<IndicatorCircle style="info" />
|
||||
)}
|
||||
{!this.stackComponentExpanded(component) && <IndicatorCircle style="neutral" />}
|
||||
</div>
|
||||
<div className="sk-app-bar-item-column">
|
||||
<div className="sk-label">{component.name}</div>
|
||||
<div className="flex items-center h-full [&:not(:first-child)]:ml-2">
|
||||
<div className="font-bold whitespace-nowrap text-xs">{component.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ const NotesContextMenu = ({
|
||||
return contextMenuOpen ? (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className="sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed"
|
||||
className="bg-default rounded shadow-main min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed z-dropdown-menu"
|
||||
style={{
|
||||
...contextMenuPosition,
|
||||
maxHeight: contextMenuMaxHeight,
|
||||
|
||||
@@ -66,13 +66,13 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={menuButtonRef}
|
||||
className="sn-dropdown-item justify-between"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="hashtag" className="mr-2 color-neutral" />
|
||||
<Icon type="hashtag" className="mr-2 text-neutral" />
|
||||
Add tag
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
@@ -86,12 +86,14 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto"
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} flex-col py-2 bg-default rounded shadow-main min-w-80 max-h-120 max-w-xs fixed overflow-y-auto`}
|
||||
>
|
||||
{navigationController.tags.map((tag) => (
|
||||
<button
|
||||
key={tag.uuid}
|
||||
className="sn-dropdown-item sn-dropdown-item--no-icon max-w-80"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-2 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item max-w-80"
|
||||
onBlur={closeOnBlur}
|
||||
onClick={() => {
|
||||
notesController.isTagInSelectedNotes(tag)
|
||||
|
||||
@@ -50,7 +50,7 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
|
||||
setMenuStyle(newMenuStyle)
|
||||
setIsVisible(true)
|
||||
}
|
||||
})
|
||||
}, 5)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
@@ -65,13 +65,13 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="sn-dropdown-item justify-between"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="dashboard" className="color-neutral mr-2" />
|
||||
<Icon type="dashboard" className="text-neutral mr-2" />
|
||||
Change note type
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
@@ -85,7 +85,7 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className="sn-dropdown flex flex-col max-h-120 min-w-68 fixed overflow-y-auto"
|
||||
className="bg-default rounded shadow-main flex flex-col max-h-120 min-w-68 fixed overflow-y-auto"
|
||||
>
|
||||
{isOpen && (
|
||||
<ChangeEditorMenu
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Action, SNNote } from '@standardnotes/snjs'
|
||||
import { Fragment, useCallback, useEffect, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { ListedMenuGroup } from './ListedMenuGroup'
|
||||
import ListedMenuItem from './ListedMenuItem'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
|
||||
type ListedActionsMenuProps = {
|
||||
application: WebApplication
|
||||
note: SNNote
|
||||
recalculateMenuStyle: () => void
|
||||
}
|
||||
|
||||
const ListedActionsMenu = ({ application, note, recalculateMenuStyle }: ListedActionsMenuProps) => {
|
||||
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
|
||||
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
|
||||
|
||||
const reloadMenuGroup = useCallback(
|
||||
async (group: ListedMenuGroup) => {
|
||||
const updatedAccountInfo = await application.getListedAccountInfo(group.account, note.uuid)
|
||||
|
||||
if (!updatedAccountInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedGroup: ListedMenuGroup = {
|
||||
name: updatedAccountInfo.display_name,
|
||||
account: group.account,
|
||||
actions: updatedAccountInfo.actions as Action[],
|
||||
}
|
||||
|
||||
const updatedGroups = menuGroups.map((group) => {
|
||||
if (updatedGroup.account.authorId === group.account.authorId) {
|
||||
return updatedGroup
|
||||
} else {
|
||||
return group
|
||||
}
|
||||
})
|
||||
|
||||
setMenuGroups(updatedGroups)
|
||||
},
|
||||
[application, menuGroups, note],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchListedAccounts = async () => {
|
||||
if (!application.hasAccount()) {
|
||||
setIsFetchingAccounts(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const listedAccountEntries = await application.getListedAccounts()
|
||||
|
||||
if (!listedAccountEntries.length) {
|
||||
throw new Error('No Listed accounts found')
|
||||
}
|
||||
|
||||
const menuGroups: ListedMenuGroup[] = []
|
||||
|
||||
await Promise.all(
|
||||
listedAccountEntries.map(async (account) => {
|
||||
const accountInfo = await application.getListedAccountInfo(account, note.uuid)
|
||||
|
||||
if (accountInfo) {
|
||||
menuGroups.push({
|
||||
name: accountInfo.display_name,
|
||||
account,
|
||||
actions: accountInfo.actions as Action[],
|
||||
})
|
||||
} else {
|
||||
menuGroups.push({
|
||||
name: account.authorId,
|
||||
account,
|
||||
actions: [],
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
setMenuGroups(
|
||||
menuGroups.sort((a, b) => {
|
||||
return a.name.toString().toLowerCase() < b.name.toString().toLowerCase() ? -1 : 1
|
||||
}),
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
setIsFetchingAccounts(false)
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
void fetchListedAccounts()
|
||||
}, [application, note.uuid, recalculateMenuStyle])
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFetchingAccounts && (
|
||||
<div className="w-full flex items-center justify-center p-4">
|
||||
<Spinner className="w-5 h-5" />
|
||||
</div>
|
||||
)}
|
||||
{!isFetchingAccounts && menuGroups.length ? (
|
||||
<>
|
||||
{menuGroups.map((group, index) => (
|
||||
<Fragment key={group.account.authorId}>
|
||||
<div
|
||||
className={`w-full flex items-center px-2.5 py-2 text-input font-semibold text-text border-y border-solid border-border ${
|
||||
index === 0 ? 'border-t-0 mb-1' : 'my-1'
|
||||
}`}
|
||||
>
|
||||
<Icon type="notes" className="mr-2 text-info" /> {group.name}
|
||||
</div>
|
||||
{group.actions.length ? (
|
||||
group.actions.map((action) => (
|
||||
<ListedMenuItem
|
||||
action={action}
|
||||
note={note}
|
||||
key={action.url}
|
||||
group={group}
|
||||
application={application}
|
||||
reloadMenuGroup={reloadMenuGroup}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="px-3 py-2 text-sm text-passive-0 select-none">No actions available</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
{!isFetchingAccounts && !menuGroups.length ? (
|
||||
<div className="w-full flex items-center justify-center px-4 py-6">
|
||||
<div className="text-sm text-passive-0 select-none">No Listed accounts found</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListedActionsMenu
|
||||
@@ -1,210 +1,17 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { Action, ListedAccount, SNNote } from '@standardnotes/snjs'
|
||||
import { Fragment, FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import ListedActionsMenu from './ListedActionsMenu'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
note: SNNote
|
||||
}
|
||||
|
||||
type ListedMenuGroup = {
|
||||
name: string
|
||||
account: ListedAccount
|
||||
actions: Action[]
|
||||
}
|
||||
|
||||
type ListedMenuItemProps = {
|
||||
action: Action
|
||||
note: SNNote
|
||||
group: ListedMenuGroup
|
||||
application: WebApplication
|
||||
reloadMenuGroup: (group: ListedMenuGroup) => Promise<void>
|
||||
}
|
||||
|
||||
const ListedMenuItem: FunctionComponent<ListedMenuItemProps> = ({
|
||||
action,
|
||||
note,
|
||||
application,
|
||||
group,
|
||||
reloadMenuGroup,
|
||||
}) => {
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
if (isRunning) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsRunning(true)
|
||||
|
||||
await application.actionsManager.runAction(action, note)
|
||||
|
||||
setIsRunning(false)
|
||||
|
||||
reloadMenuGroup(group).catch(console.error)
|
||||
}, [application, action, group, isRunning, note, reloadMenuGroup])
|
||||
|
||||
return (
|
||||
<button
|
||||
key={action.url}
|
||||
onClick={handleClick}
|
||||
className="sn-dropdown-item flex justify-between py-2 text-input focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="font-semibold">{action.label}</div>
|
||||
{action.access_type && (
|
||||
<div className="text-xs mt-0.5 color-passive-0">
|
||||
{'Uses '}
|
||||
<strong>{action.access_type}</strong>
|
||||
{' access to this note.'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isRunning && <div className="sk-spinner spinner-info w-3 h-3" />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
type ListedActionsMenuProps = {
|
||||
application: WebApplication
|
||||
note: SNNote
|
||||
recalculateMenuStyle: () => void
|
||||
}
|
||||
|
||||
const ListedActionsMenu: FunctionComponent<ListedActionsMenuProps> = ({ application, note, recalculateMenuStyle }) => {
|
||||
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
|
||||
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
|
||||
|
||||
const reloadMenuGroup = useCallback(
|
||||
async (group: ListedMenuGroup) => {
|
||||
const updatedAccountInfo = await application.getListedAccountInfo(group.account, note.uuid)
|
||||
|
||||
if (!updatedAccountInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedGroup: ListedMenuGroup = {
|
||||
name: updatedAccountInfo.display_name,
|
||||
account: group.account,
|
||||
actions: updatedAccountInfo.actions as Action[],
|
||||
}
|
||||
|
||||
const updatedGroups = menuGroups.map((group) => {
|
||||
if (updatedGroup.account.authorId === group.account.authorId) {
|
||||
return updatedGroup
|
||||
} else {
|
||||
return group
|
||||
}
|
||||
})
|
||||
|
||||
setMenuGroups(updatedGroups)
|
||||
},
|
||||
[application, menuGroups, note],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchListedAccounts = async () => {
|
||||
if (!application.hasAccount()) {
|
||||
setIsFetchingAccounts(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const listedAccountEntries = await application.getListedAccounts()
|
||||
|
||||
if (!listedAccountEntries.length) {
|
||||
throw new Error('No Listed accounts found')
|
||||
}
|
||||
|
||||
const menuGroups: ListedMenuGroup[] = []
|
||||
|
||||
await Promise.all(
|
||||
listedAccountEntries.map(async (account) => {
|
||||
const accountInfo = await application.getListedAccountInfo(account, note.uuid)
|
||||
|
||||
if (accountInfo) {
|
||||
menuGroups.push({
|
||||
name: accountInfo.display_name,
|
||||
account,
|
||||
actions: accountInfo.actions as Action[],
|
||||
})
|
||||
} else {
|
||||
menuGroups.push({
|
||||
name: account.authorId,
|
||||
account,
|
||||
actions: [],
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
setMenuGroups(
|
||||
menuGroups.sort((a, b) => {
|
||||
return a.name.toString().toLowerCase() < b.name.toString().toLowerCase() ? -1 : 1
|
||||
}),
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
setIsFetchingAccounts(false)
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
void fetchListedAccounts()
|
||||
}, [application, note.uuid, recalculateMenuStyle])
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFetchingAccounts && (
|
||||
<div className="w-full flex items-center justify-center p-4">
|
||||
<div className="sk-spinner w-5 h-5 spinner-info" />
|
||||
</div>
|
||||
)}
|
||||
{!isFetchingAccounts && menuGroups.length ? (
|
||||
<>
|
||||
{menuGroups.map((group, index) => (
|
||||
<Fragment key={group.account.authorId}>
|
||||
<div
|
||||
className={`w-full flex items-center px-2.5 py-2 text-input font-semibold color-text border-0 border-y-1px border-solid border-main ${
|
||||
index === 0 ? 'border-t-0 mb-1' : 'my-1'
|
||||
}`}
|
||||
>
|
||||
<Icon type="notes" className="mr-2 color-info" /> {group.name}
|
||||
</div>
|
||||
{group.actions.length ? (
|
||||
group.actions.map((action) => (
|
||||
<ListedMenuItem
|
||||
action={action}
|
||||
note={note}
|
||||
key={action.url}
|
||||
group={group}
|
||||
application={application}
|
||||
reloadMenuGroup={reloadMenuGroup}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="px-3 py-2 color-passive-0 select-none">No actions available</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
{!isFetchingAccounts && !menuGroups.length ? (
|
||||
<div className="w-full flex items-center justify-center px-4 py-6">
|
||||
<div className="color-passive-0 select-none">No Listed accounts found</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) => {
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
@@ -249,12 +56,16 @@ const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) =>
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<Disclosure open={isMenuOpen} onChange={toggleListedMenu}>
|
||||
<DisclosureButton ref={menuButtonRef} onBlur={closeOnBlur} className="sn-dropdown-item justify-between">
|
||||
<DisclosureButton
|
||||
ref={menuButtonRef}
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="listed" className="color-neutral mr-2" />
|
||||
<Icon type="listed" className="text-neutral mr-2" />
|
||||
Listed actions
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
@@ -262,7 +73,9 @@ const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) =>
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className="sn-dropdown flex flex-col max-h-120 min-w-68 pb-1 fixed overflow-y-auto"
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} flex-col bg-default rounded shadow-main max-h-120 min-w-68 pb-1 fixed overflow-y-auto`}
|
||||
>
|
||||
{isMenuOpen && (
|
||||
<ListedActionsMenu application={application} note={note} recalculateMenuStyle={recalculateMenuStyle} />
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Action, ListedAccount } from '@standardnotes/snjs'
|
||||
|
||||
export type ListedMenuGroup = {
|
||||
name: string
|
||||
account: ListedAccount
|
||||
actions: Action[]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Action, SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useState } from 'react'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import { ListedMenuGroup } from './ListedMenuGroup'
|
||||
|
||||
type ListedMenuItemProps = {
|
||||
action: Action
|
||||
note: SNNote
|
||||
group: ListedMenuGroup
|
||||
application: WebApplication
|
||||
reloadMenuGroup: (group: ListedMenuGroup) => Promise<void>
|
||||
}
|
||||
|
||||
const ListedMenuItem: FunctionComponent<ListedMenuItemProps> = ({
|
||||
action,
|
||||
note,
|
||||
application,
|
||||
group,
|
||||
reloadMenuGroup,
|
||||
}) => {
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
if (isRunning) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsRunning(true)
|
||||
|
||||
await application.actionsManager.runAction(action, note)
|
||||
|
||||
setIsRunning(false)
|
||||
|
||||
reloadMenuGroup(group).catch(console.error)
|
||||
}, [application, action, group, isRunning, note, reloadMenuGroup])
|
||||
|
||||
return (
|
||||
<button
|
||||
key={action.url}
|
||||
onClick={handleClick}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-2 text-left w-full focus:bg-info-backdrop focus:shadow-none text-sm"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="font-semibold">{action.label}</div>
|
||||
{action.access_type && (
|
||||
<div className="text-xs mt-0.5 text-passive-0">
|
||||
{'Uses '}
|
||||
<strong>{action.access_type}</strong>
|
||||
{' access to this note.'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isRunning && <Spinner className="w-3 h-3" />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListedMenuItem
|
||||
@@ -11,6 +11,7 @@ import AddTagOption from './AddTagOption'
|
||||
import { addToast, dismissToast, ToastType } from '@standardnotes/toast'
|
||||
import { NotesOptionsProps } from './NotesOptionsProps'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
|
||||
type DeletePermanentlyButtonProps = {
|
||||
closeOnBlur: NotesOptionsProps['closeOnBlur']
|
||||
@@ -18,16 +19,20 @@ type DeletePermanentlyButtonProps = {
|
||||
}
|
||||
|
||||
const DeletePermanentlyButton = ({ closeOnBlur, onClick }: DeletePermanentlyButtonProps) => (
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={onClick}>
|
||||
<Icon type="close" className="color-danger mr-2" />
|
||||
<span className="color-danger">Delete permanently</span>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon type="close" className="text-danger mr-2" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
const iconClass = 'color-neutral mr-2'
|
||||
const iconClassDanger = 'color-danger mr-2'
|
||||
const iconClassWarning = 'color-warning mr-2'
|
||||
const iconClassSuccess = 'color-success mr-2'
|
||||
const iconClass = 'text-neutral mr-2'
|
||||
const iconClassDanger = 'text-danger mr-2'
|
||||
const iconClassWarning = 'text-warning mr-2'
|
||||
const iconClassSuccess = 'text-success mr-2'
|
||||
|
||||
const getWordCount = (text: string) => {
|
||||
if (text.trim().length === 0) {
|
||||
@@ -96,7 +101,7 @@ const NoteAttributes: FunctionComponent<{
|
||||
const format = editor?.package_info?.file_type || 'txt'
|
||||
|
||||
return (
|
||||
<div className="px-3 pt-1.5 pb-2.5 text-xs color-neutral font-medium">
|
||||
<div className="px-3 pt-1.5 pb-2.5 text-xs text-neutral font-medium">
|
||||
{typeof words === 'number' && (format === 'txt' || format === 'md') ? (
|
||||
<>
|
||||
<div className="mb-1">
|
||||
@@ -135,7 +140,7 @@ const SpellcheckOptions: FunctionComponent<{
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<button
|
||||
className="sn-dropdown-item justify-between px-3 py-1"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
onClick={() => {
|
||||
notesController.toggleGlobalSpellcheckForNote(note).catch(console.error)
|
||||
}}
|
||||
@@ -161,8 +166,8 @@ const NoteSizeWarning: FunctionComponent<{
|
||||
}> = ({ note }) => {
|
||||
return new Blob([note.text]).size > NOTE_SIZE_WARNING_THRESHOLD ? (
|
||||
<div className="flex items-center px-3 py-3.5 relative bg-warning-faded">
|
||||
<Icon type="warning" className="color-accessory-tint-3 flex-shrink-0 mr-3" />
|
||||
<div className="color-warning select-none leading-140% max-w-80%">
|
||||
<Icon type="warning" className="text-accessory-tint-3 flex-shrink-0 mr-3" />
|
||||
<div className="text-warning select-none leading-140% max-w-80%">
|
||||
This note may have trouble syncing to the mobile application due to its size.
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,15 +272,19 @@ const NotesOptions = ({
|
||||
<>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={openRevisionHistoryModal}>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={openRevisionHistoryModal}
|
||||
>
|
||||
<Icon type="history" className={iconClass} />
|
||||
Note history
|
||||
</button>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
onClick={() => {
|
||||
notesController.setLockSelectedNotes(!locked)
|
||||
}}
|
||||
@@ -288,7 +297,7 @@ const NotesOptions = ({
|
||||
<Switch className="px-0" checked={locked} />
|
||||
</button>
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
onClick={() => {
|
||||
notesController.setHideSelectedNotePreviews(!hidePreviews)
|
||||
}}
|
||||
@@ -301,7 +310,7 @@ const NotesOptions = ({
|
||||
<Switch className="px-0" checked={!hidePreviews} />
|
||||
</button>
|
||||
<button
|
||||
className="sn-dropdown-item justify-between"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item justify-between"
|
||||
onClick={() => {
|
||||
notesController.setProtectSelectedNotes(!protect).catch(console.error)
|
||||
}}
|
||||
@@ -315,11 +324,11 @@ const NotesOptions = ({
|
||||
</button>
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<ChangeEditorOption application={application} note={notes[0]} />
|
||||
</>
|
||||
)}
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
{navigationController.tagsCount > 0 && (
|
||||
<AddTagOption
|
||||
navigationController={navigationController}
|
||||
@@ -330,7 +339,7 @@ const NotesOptions = ({
|
||||
{unpinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={() => {
|
||||
notesController.setPinSelectedNotes(true)
|
||||
}}
|
||||
@@ -342,7 +351,7 @@ const NotesOptions = ({
|
||||
{pinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={() => {
|
||||
notesController.setPinSelectedNotes(false)
|
||||
}}
|
||||
@@ -351,36 +360,44 @@ const NotesOptions = ({
|
||||
Unpin
|
||||
</button>
|
||||
)}
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={downloadSelectedItems}>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={downloadSelectedItems}
|
||||
>
|
||||
<Icon type="download" className={iconClass} />
|
||||
Export
|
||||
</button>
|
||||
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={duplicateSelectedItems}>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={duplicateSelectedItems}
|
||||
>
|
||||
<Icon type="copy" className={iconClass} />
|
||||
Duplicate
|
||||
</button>
|
||||
{unarchived && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={() => {
|
||||
notesController.setArchiveSelectedNotes(true).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<Icon type="archive" className={iconClassWarning} />
|
||||
<span className="color-warning">Archive</span>
|
||||
<span className="text-warning">Archive</span>
|
||||
</button>
|
||||
)}
|
||||
{archived && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={() => {
|
||||
notesController.setArchiveSelectedNotes(false).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<Icon type="unarchive" className={iconClassWarning} />
|
||||
<span className="color-warning">Unarchive</span>
|
||||
<span className="text-warning">Unarchive</span>
|
||||
</button>
|
||||
)}
|
||||
{notTrashed &&
|
||||
@@ -394,26 +411,26 @@ const NotesOptions = ({
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={async () => {
|
||||
await notesController.setTrashSelectedNotes(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className={iconClassDanger} />
|
||||
<span className="color-danger">Move to trash</span>
|
||||
<span className="text-danger">Move to trash</span>
|
||||
</button>
|
||||
))}
|
||||
{trashed && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={async () => {
|
||||
await notesController.setTrashSelectedNotes(false)
|
||||
}}
|
||||
>
|
||||
<Icon type="restore" className={iconClassSuccess} />
|
||||
<span className="color-success">Restore</span>
|
||||
<span className="text-success">Restore</span>
|
||||
</button>
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
@@ -423,15 +440,15 @@ const NotesOptions = ({
|
||||
/>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="sn-dropdown-item"
|
||||
className="flex items-center border-0 cursor-pointer hover:bg-contrast hover:text-foreground text-text bg-transparent px-3 py-1.5 text-left w-full focus:bg-info-backdrop focus:shadow-none text-menu-item"
|
||||
onClick={async () => {
|
||||
await notesController.emptyTrash()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start">
|
||||
<Icon type="trash-sweep" className="color-danger mr-2" />
|
||||
<Icon type="trash-sweep" className="text-danger mr-2" />
|
||||
<div className="flex-row">
|
||||
<div className="color-danger">Empty Trash</div>
|
||||
<div className="text-danger">Empty Trash</div>
|
||||
<div className="text-xs">{notesController.trashedNotesCount} notes in Trash</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -440,11 +457,11 @@ const NotesOptions = ({
|
||||
)}
|
||||
{notes.length === 1 ? (
|
||||
<>
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<ListedActionsOption application={application} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<SpellcheckOptions editorForNote={editorForNote} notesController={notesController} note={notes[0]} />
|
||||
<div className="min-h-1px my-2 bg-border"></div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<NoteAttributes application={application} note={notes[0]} />
|
||||
<NoteSizeWarning note={notes[0]} />
|
||||
</>
|
||||
|
||||
@@ -71,7 +71,7 @@ const NotesOptionsPanel = ({
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="sn-icon-button border-contrast"
|
||||
className="flex justify-center items-center min-w-8 h-8 bg-text-padding hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer"
|
||||
>
|
||||
<VisuallyHidden>Actions</VisuallyHidden>
|
||||
<Icon type="more" className="block" />
|
||||
@@ -88,7 +88,9 @@ const NotesOptionsPanel = ({
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed"
|
||||
className={`${
|
||||
open ? 'flex' : 'hidden'
|
||||
} flex-col min-w-80 max-h-120 max-w-xs pt-2 fixed bg-default rounded shadow-main transition-transform duration-150 slide-down-animation overflow-y-auto`}
|
||||
onBlur={closeOnBlur}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AlertDialog, AlertDialogDescription, AlertDialogLabel } from '@reach/al
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -17,7 +18,7 @@ const ConfirmOtherSessionsSignOut = observer(({ application, viewControllerManag
|
||||
}, [viewControllerManager])
|
||||
|
||||
return (
|
||||
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
|
||||
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef} className="p-0 max-w-[600px]">
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
@@ -27,18 +28,21 @@ const ConfirmOtherSessionsSignOut = observer(({ application, viewControllerManag
|
||||
End all other sessions?
|
||||
</AlertDialogLabel>
|
||||
<AlertDialogDescription className="sk-panel-row">
|
||||
<p className="color-foreground">
|
||||
<p className="text-foreground">
|
||||
This action will sign out all other devices signed into your account, and remove your data from
|
||||
those devices when they next regain connection to the internet. You may sign back in on those
|
||||
devices at any time.
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
<div className="flex my-1 mt-4">
|
||||
<button className="sn-button small neutral" ref={cancelRef} onClick={closeDialog}>
|
||||
<div className="flex my-1 mt-4 gap-2">
|
||||
<Button primary small colorStyle="neutral" rounded={false} ref={cancelRef} onClick={closeDialog}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="sn-button small danger ml-2"
|
||||
</Button>
|
||||
<Button
|
||||
primary
|
||||
small
|
||||
colorStyle="danger"
|
||||
rounded={false}
|
||||
onClick={() => {
|
||||
application.revokeAllOtherSessions().catch(console.error)
|
||||
closeDialog()
|
||||
@@ -48,7 +52,7 @@ const ConfirmOtherSessionsSignOut = observer(({ application, viewControllerManag
|
||||
}}
|
||||
>
|
||||
End Sessions
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ChangeEventHandler, createRef } from 'react'
|
||||
import { createRef } from 'react'
|
||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import DecoratedPasswordInput from '../Input/DecoratedPasswordInput'
|
||||
|
||||
interface Props {
|
||||
application: WebApplication
|
||||
@@ -202,21 +204,21 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
})
|
||||
}
|
||||
|
||||
handleCurrentPasswordInputChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) => {
|
||||
handleCurrentPasswordInputChange = (currentPassword: string) => {
|
||||
this.setFormDataState({
|
||||
currentPassword: currentTarget.value,
|
||||
currentPassword,
|
||||
}).catch(console.error)
|
||||
}
|
||||
|
||||
handleNewPasswordInputChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) => {
|
||||
handleNewPasswordInputChange = (newPassword: string) => {
|
||||
this.setFormDataState({
|
||||
newPassword: currentTarget.value,
|
||||
newPassword,
|
||||
}).catch(console.error)
|
||||
}
|
||||
|
||||
handleNewPasswordConfirmationInputChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) => {
|
||||
handleNewPasswordConfirmationInputChange = (newPasswordConfirmation: string) => {
|
||||
this.setFormDataState({
|
||||
newPasswordConfirmation: currentTarget.value,
|
||||
newPasswordConfirmation,
|
||||
}).catch(console.error)
|
||||
}
|
||||
|
||||
@@ -244,13 +246,12 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
Current Password
|
||||
</label>
|
||||
|
||||
<input
|
||||
<DecoratedPasswordInput
|
||||
ref={this.currentPasswordInput}
|
||||
id="password-wiz-current-password"
|
||||
value={this.state.formData.currentPassword}
|
||||
onChange={this.handleCurrentPasswordInputChange}
|
||||
type="password"
|
||||
className="sk-input contrast"
|
||||
/>
|
||||
|
||||
<div className="sk-panel-row" />
|
||||
@@ -259,12 +260,11 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
New Password
|
||||
</label>
|
||||
|
||||
<input
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-new-password"
|
||||
value={this.state.formData.newPassword}
|
||||
onChange={this.handleNewPasswordInputChange}
|
||||
type="password"
|
||||
className="sk-input contrast"
|
||||
/>
|
||||
<div className="sk-panel-row" />
|
||||
|
||||
@@ -272,12 +272,11 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
Confirm New Password
|
||||
</label>
|
||||
|
||||
<input
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-confirm-new-password"
|
||||
value={this.state.formData.newPasswordConfirmation}
|
||||
onChange={this.handleNewPasswordConfirmationInputChange}
|
||||
type="password"
|
||||
className="sk-input contrast"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
@@ -286,7 +285,7 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
)}
|
||||
{this.state.step === Steps.FinishStep && (
|
||||
<div className="sk-panel-section">
|
||||
<div className="sk-label sk-bold info">Your password has been successfully changed.</div>
|
||||
<div className="font-bold text-info mb-1">Your password has been successfully changed.</div>
|
||||
<p className="sk-p">
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure
|
||||
maximum compatibility.
|
||||
@@ -295,13 +294,9 @@ class PasswordWizard extends PureComponent<Props, State> {
|
||||
)}
|
||||
</div>
|
||||
<div className="sk-panel-footer">
|
||||
<button
|
||||
onClick={this.nextStep}
|
||||
disabled={this.state.lockContinue}
|
||||
className="sn-button min-w-20 info"
|
||||
>
|
||||
<Button primary onClick={this.nextStep} disabled={this.state.lockContinue} className="min-w-20">
|
||||
{this.state.continueTitle}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { SNComponent } from '@standardnotes/snjs'
|
||||
import { Component } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import ModalDialog from '../Shared/ModalDialog'
|
||||
import ModalDialogLabel from '../Shared/ModalDialogLabel'
|
||||
import ModalDialogDescription from '../Shared/ModalDialogDescription'
|
||||
import ModalDialogButtons from '../Shared/ModalDialogButtons'
|
||||
|
||||
interface Props {
|
||||
application: WebApplication
|
||||
@@ -23,50 +28,29 @@ class PermissionsModal extends Component<Props> {
|
||||
|
||||
override render() {
|
||||
return (
|
||||
<div className="sk-modal">
|
||||
<div onClick={this.deny} className="sk-modal-background" />
|
||||
<div id="permissions-modal" className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
<div className="sk-panel-header">
|
||||
<div className="sk-panel-header-title">Activate Component</div>
|
||||
<a onClick={this.deny} className="sk-a info close-button">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<div className="sk-panel-row">
|
||||
<div className="sk-h2">
|
||||
<strong>{this.props.component.displayName}</strong>
|
||||
{' would like to interact with your '}
|
||||
{this.props.permissionsString}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-panel-row">
|
||||
<p className="sk-p">
|
||||
Components use an offline messaging system to communicate. Learn more at{' '}
|
||||
<a
|
||||
href="https://standardnotes.com/permissions"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
className="sk-a info"
|
||||
>
|
||||
https://standardnotes.com/permissions.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-panel-footer">
|
||||
<button onClick={this.accept} className="sn-button info block w-full text-base py-3">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalDialog className="w-[350px]">
|
||||
<ModalDialogLabel closeDialog={this.deny}>Activate Component</ModalDialogLabel>
|
||||
<ModalDialogDescription>
|
||||
<div className="text-base">
|
||||
<strong>{this.props.component.displayName}</strong>
|
||||
{' would like to interact with your '}
|
||||
{this.props.permissionsString}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-panel-row">
|
||||
<p className="sk-p">
|
||||
Components use an offline messaging system to communicate. Learn more at{' '}
|
||||
<a href="https://standardnotes.com/permissions" rel="noopener" target="_blank" className="sk-a info">
|
||||
https://standardnotes.com/permissions.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons>
|
||||
<Button primary fullWidth onClick={this.accept} className="block">
|
||||
Continue
|
||||
</Button>
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,12 @@ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesControll
|
||||
}, [onClickPreprocessing, pinned, notesController])
|
||||
|
||||
return (
|
||||
<button className={`sn-icon-button border-contrast ${pinned ? 'toggled' : ''} ${className}`} onClick={togglePinned}>
|
||||
<button
|
||||
className={`sn-icon-button flex justify-center items-center min-w-8 h-8 hover:bg-contrast focus:bg-contrast text-neutral border border-solid border-border rounded-full cursor-pointer ${
|
||||
pinned ? 'toggled' : ''
|
||||
} ${className}`}
|
||||
onClick={togglePinned}
|
||||
>
|
||||
<VisuallyHidden>Pin selected notes</VisuallyHidden>
|
||||
<Icon type="pin" className="block" />
|
||||
</button>
|
||||
|
||||
@@ -36,10 +36,10 @@ const Authentication: FunctionComponent<Props> = ({ viewControllerManager }) =>
|
||||
<Text className="text-center mb-3">
|
||||
Sign in to sync your notes and preferences across all your devices and enable end-to-end encryption.
|
||||
</Text>
|
||||
<Button variant="primary" label="Create free account" onClick={clickRegister} className="mb-3" />
|
||||
<div className="text-input">
|
||||
<Button primary label="Create free account" onClick={clickRegister} className="mb-3" />
|
||||
<div className="text-sm">
|
||||
Already have an account?{' '}
|
||||
<button className="border-0 p-0 bg-default color-info underline cursor-pointer" onClick={clickSignIn}>
|
||||
<button className="border-0 p-0 bg-default text-info underline cursor-pointer" onClick={clickSignIn}>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -142,14 +142,14 @@ const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) =
|
||||
<div>
|
||||
<ModalDialog>
|
||||
<ModalDialogLabel closeDialog={handleDialogClose}>Change Email</ModalDialogLabel>
|
||||
<ModalDialogDescription className="px-4.5">
|
||||
<ModalDialogDescription className="px-4.5 flex flex-row items-center">
|
||||
{currentStep === Steps.InitialStep && (
|
||||
<ChangeEmailForm setNewEmail={setNewEmail} setCurrentPassword={setCurrentPassword} />
|
||||
)}
|
||||
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||
</ModalDialogDescription>
|
||||
<ModalDialogButtons className="px-4.5">
|
||||
<Button className="min-w-20" variant="primary" label={submitButtonTitle} onClick={handleSubmit} />
|
||||
<Button className="min-w-20" primary label={submitButtonTitle} onClick={handleSubmit} />
|
||||
</ModalDialogButtons>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput'
|
||||
import { Dispatch, SetStateAction, FunctionComponent } from 'react'
|
||||
|
||||
type Props = {
|
||||
@@ -7,8 +9,6 @@ type Props = {
|
||||
|
||||
const labelClassName = 'block mb-1'
|
||||
|
||||
const inputClassName = 'sk-input contrast'
|
||||
|
||||
const ChangeEmailForm: FunctionComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
|
||||
return (
|
||||
<div className="w-full flex flex-col">
|
||||
@@ -16,12 +16,11 @@ const ChangeEmailForm: FunctionComponent<Props> = ({ setNewEmail, setCurrentPass
|
||||
<label className={labelClassName} htmlFor="change-email-email-input">
|
||||
New Email:
|
||||
</label>
|
||||
<input
|
||||
id="change-email-email-input"
|
||||
className={inputClassName}
|
||||
<DecoratedInput
|
||||
type="email"
|
||||
onChange={({ target }) => {
|
||||
setNewEmail((target as HTMLInputElement).value)
|
||||
id="change-email-email-input"
|
||||
onChange={(newEmail) => {
|
||||
setNewEmail(newEmail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -29,12 +28,11 @@ const ChangeEmailForm: FunctionComponent<Props> = ({ setNewEmail, setCurrentPass
|
||||
<label className={labelClassName} htmlFor="change-email-password-input">
|
||||
Current Password:
|
||||
</label>
|
||||
<input
|
||||
<DecoratedPasswordInput
|
||||
id="change-email-password-input"
|
||||
className={inputClassName}
|
||||
type="password"
|
||||
onChange={({ target }) => {
|
||||
setCurrentPassword((target as HTMLInputElement).value)
|
||||
onChange={(currentPassword) => {
|
||||
setCurrentPassword(currentPassword)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,8 @@ import { FunctionComponent } from 'react'
|
||||
const ChangeEmailSuccess: FunctionComponent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
|
||||
<p className={'sk-p'}>
|
||||
<div className={'font-bold text-info mb-2'}>Your email has been successfully changed.</div>
|
||||
<p>
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum
|
||||
compatibility.
|
||||
</p>
|
||||
|
||||
@@ -13,10 +13,9 @@ const ClearSessionDataView: FunctionComponent<{
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Clear workspace</Title>
|
||||
<Text>Remove all data related to the current workspace from the application.</Text>
|
||||
<div className="min-h-3" />
|
||||
<Text className="mb-3">Remove all data related to the current workspace from the application.</Text>
|
||||
<Button
|
||||
dangerStyle={true}
|
||||
colorStyle="danger"
|
||||
label="Clear workspace"
|
||||
onClick={() => {
|
||||
viewControllerManager.accountMenuController.setSigningOut(true)
|
||||
|
||||
@@ -44,7 +44,6 @@ const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
</Text>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
variant="normal"
|
||||
label="Change email"
|
||||
onClick={() => {
|
||||
setIsChangeEmailDialogOpen(true)
|
||||
@@ -55,7 +54,7 @@ const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
|
||||
<Text>
|
||||
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
||||
</Text>
|
||||
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
|
||||
<Button className="min-w-20 mt-3" label="Change password" onClick={presentPasswordWizard} />
|
||||
{isChangeEmailDialogOpen && (
|
||||
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user