diff --git a/.eslintignore b/.eslintignore index ae4fbcc60..25c41fd03 100644 --- a/.eslintignore +++ b/.eslintignore @@ -386,7 +386,9 @@ packages/app-desktop/gui/NoteTextViewer.js packages/app-desktop/gui/NoteToolbar/NoteToolbar.js packages/app-desktop/gui/NotyfContext.js packages/app-desktop/gui/OneDriveLoginScreen.js +packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.js packages/app-desktop/gui/PasswordInput/PasswordInput.js +packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js diff --git a/.gitignore b/.gitignore index 36b0e0f8d..0cd35145c 100644 --- a/.gitignore +++ b/.gitignore @@ -363,7 +363,9 @@ packages/app-desktop/gui/NoteTextViewer.js packages/app-desktop/gui/NoteToolbar/NoteToolbar.js packages/app-desktop/gui/NotyfContext.js packages/app-desktop/gui/OneDriveLoginScreen.js +packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.js packages/app-desktop/gui/PasswordInput/PasswordInput.js +packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js diff --git a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx index e23f9bf42..95ac26184 100644 --- a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx +++ b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx @@ -10,7 +10,7 @@ import { reg } from '@joplin/lib/registry'; import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import KvStore from '@joplin/lib/services/KvStore'; import ShareService from '@joplin/lib/services/share/ShareService'; -import { PasswordInput } from '../PasswordInput/PasswordInput'; +import LabelledPasswordInput from '../PasswordInput/LabelledPasswordInput'; interface Props { themeId: number; @@ -136,11 +136,6 @@ export default function(props: Props) { setCurrentPasswordIsValid(isValid); }, [currentPassword]); - function renderCurrentPasswordIcon() { - if (!currentPassword || status === MasterPasswordStatus.NotSet) return null; - return currentPasswordIsValid ? : ; - } - function renderPasswordForm() { const renderCurrentPassword = () => { if (!showCurrentPassword) return null; @@ -151,14 +146,14 @@ export default function(props: Props) { // having to reset the password (and lose access to any data that's // been encrypted with it). + const showValidIcon = currentPassword && status !== MasterPasswordStatus.NotSet; return ( -
- -
- - {renderCurrentPasswordIcon()} -
-
+ ); }; @@ -175,15 +170,17 @@ export default function(props: Props) {
{renderCurrentPassword()} -
- - -
+ {needToRepeatPassword && ( -
- - -
+ )}

Please make sure you remember your password. For security reasons, it is not possible to recover it if it is lost.

diff --git a/packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.tsx b/packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.tsx new file mode 100644 index 000000000..d902f52df --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import PasswordInput from './PasswordInput'; +import { useId } from 'react'; +import { ChangeEventHandler } from './types'; +import { _ } from '@joplin/lib/locale'; + +interface Props { + labelText: string; + value: string; + onChange: ChangeEventHandler; + valid?: boolean; +} + +const LabelledPasswordInput: React.FC = props => { + const inputId = useId(); + const statusIconId = useId(); + + const canRenderStatusIcon = (props.valid ?? null) !== null && props.value; + const renderStatusIcon = () => { + if (!canRenderStatusIcon) return null; + let title, classNames; + if (props.valid) { + title = _('Valid'); + classNames = 'fas fa-check -valid'; + } else { + title = _('Invalid'); + classNames = 'fas fa-times -invalid'; + } + return ; + }; + + return
+ +
+ + {renderStatusIcon()} +
+
; +}; + +export default LabelledPasswordInput; diff --git a/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx b/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx index d25fa4179..fd6dfaeab 100644 --- a/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx +++ b/packages/app-desktop/gui/PasswordInput/PasswordInput.tsx @@ -1,22 +1,24 @@ +import * as React from 'react'; import { useState, useCallback } from 'react'; import StyledInput from '../style/StyledInput'; - -export interface ChangeEvent { - value: string; -} - -type ChangeEventHandler = (event: ChangeEvent)=> void; +import { _ } from '@joplin/lib/locale'; +import { ChangeEventHandler } from './types'; interface Props { value: string; + inputId: string; onChange: ChangeEventHandler; + + 'aria-invalid'?: boolean; + 'aria-errormessage'?: string; } -export const PasswordInput = (props: Props) => { +const PasswordInput = (props: Props) => { const [showPassword, setShowPassword] = useState(false); const inputType = showPassword ? 'text' : 'password'; const icon = showPassword ? 'far fa-eye-slash' : 'far fa-eye'; + const title = showPassword ? _('Hide password') : _('Show password'); const onShowPassword = useCallback(() => { setShowPassword(current => !current); @@ -24,8 +26,20 @@ export const PasswordInput = (props: Props) => { return (
- - + +
); }; + +export default PasswordInput; diff --git a/packages/app-desktop/gui/PasswordInput/style.scss b/packages/app-desktop/gui/PasswordInput/style.scss index eba398237..95349b3a4 100644 --- a/packages/app-desktop/gui/PasswordInput/style.scss +++ b/packages/app-desktop/gui/PasswordInput/style.scss @@ -1,19 +1,3 @@ -.password-input { - display: flex; - position: relative; - flex: 1; - - > .field { - display: flex; - flex: 1; - width: 100%; - } - - > .showpasswordbutton { - position: absolute; - right: 5px; - top: 4px; - border: none; - background: none; - } -} +@use "styles/password-input.scss"; +@use "styles/labelled-password-input.scss"; +@use "styles/password-status-icon.scss"; \ No newline at end of file diff --git a/packages/app-desktop/gui/PasswordInput/styles/labelled-password-input.scss b/packages/app-desktop/gui/PasswordInput/styles/labelled-password-input.scss new file mode 100644 index 000000000..66cd3c55f --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/styles/labelled-password-input.scss @@ -0,0 +1,10 @@ +.labelled-password-input { + display: flex; + flex-direction: column; + + > .password { + display: flex; + flex-direction: row; + align-items: center; + } +} \ No newline at end of file diff --git a/packages/app-desktop/gui/PasswordInput/styles/password-input.scss b/packages/app-desktop/gui/PasswordInput/styles/password-input.scss new file mode 100644 index 000000000..eba398237 --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/styles/password-input.scss @@ -0,0 +1,19 @@ +.password-input { + display: flex; + position: relative; + flex: 1; + + > .field { + display: flex; + flex: 1; + width: 100%; + } + + > .showpasswordbutton { + position: absolute; + right: 5px; + top: 4px; + border: none; + background: none; + } +} diff --git a/packages/app-desktop/gui/PasswordInput/styles/password-status-icon.scss b/packages/app-desktop/gui/PasswordInput/styles/password-status-icon.scss new file mode 100644 index 000000000..ced186ce3 --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/styles/password-status-icon.scss @@ -0,0 +1,13 @@ + +.password-status-icon { + margin-left: 10px; + + &.-valid { + color: var(--joplin-color-correct); + } + + &.-invalid { + color: var(--joplin-color-error); + margin-left: 5px; + } +} \ No newline at end of file diff --git a/packages/app-desktop/gui/PasswordInput/types.ts b/packages/app-desktop/gui/PasswordInput/types.ts new file mode 100644 index 000000000..bb7a4da58 --- /dev/null +++ b/packages/app-desktop/gui/PasswordInput/types.ts @@ -0,0 +1,6 @@ + +export interface ChangeEvent { + value: string; +} + +export type ChangeEventHandler = (event: ChangeEvent)=> void; diff --git a/packages/app-desktop/gui/ResourceScreen.tsx b/packages/app-desktop/gui/ResourceScreen.tsx index 871078a63..ca2af7a9e 100644 --- a/packages/app-desktop/gui/ResourceScreen.tsx +++ b/packages/app-desktop/gui/ResourceScreen.tsx @@ -60,15 +60,6 @@ interface ActiveSorting { const ResourceTableComp = (props: ResourceTable) => { const theme = themeStyle(props.themeId); - const sortOrderEngagedMarker = (s: SortingOrder) => { - return ( - props.onToggleSorting(s)}>{ - (props.sorting.order === s && props.sorting.type === 'desc') ? '▾' : '▴'} - ); - }; - const titleCellStyle = { ...theme.textStyle, textOverflow: 'ellipsis', @@ -96,12 +87,28 @@ const ResourceTableComp = (props: ResourceTable) => { (resource: InnerResource) => !props.filter || resource.title?.includes(props.filter) || resource.id.includes(props.filter), ); + const renderSortableHeader = (title: string, order: SortingOrder) => { + const sortedDescending = props.sorting.order === order && props.sorting.type === 'desc'; + const sortButtonLabel = sortedDescending ? _('Sort "%s" in ascending order', title) : _('Sort "%s" in descending order', title); + const reverseSortButton = ( + props.onToggleSorting(order)} + aria-label={sortButtonLabel} + title={sortButtonLabel} + role='button' + >{sortedDescending ? '▾' : '▴'} + ); + return {title} {reverseSortButton}; + }; + return ( - - + {renderSortableHeader(_('Title'), 'name')} + {renderSortableHeader(_('Size'), 'size')} diff --git a/packages/app-desktop/main.scss b/packages/app-desktop/main.scss index ed6e1d975..c862447e5 100644 --- a/packages/app-desktop/main.scss +++ b/packages/app-desktop/main.scss @@ -289,22 +289,9 @@ Component-specific classes display: flex; flex-direction: row; align-items: center; - - > .password-valid-icon { - margin-left: 10px; - } } // .master-password-dialog .current-password-wrapper input { // flex: 1; // margin-right: 10px; // } - -.master-password-dialog .fa-check { - color: var(--joplin-color-correct); -} - -.master-password-dialog .fa-times { - color: var(--joplin-color-error); - margin-left: 5px; -}
{_('Title')} {sortOrderEngagedMarker('name')}{_('Size')} {sortOrderEngagedMarker('size')}{_('ID')} {_('Action')}