feat: multiple selected notes panel
4
app/assets/icons/ic-archive.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.4444 12.3333H12.3333C12.3333 12.9522 12.0875 13.5457 11.6499 13.9832C11.2123 14.4208 10.6188 14.6667 10 14.6667C9.38116 14.6667 8.78767 14.4208 8.35008 13.9832C7.9125 13.5457 7.66667 12.9522 7.66667 12.3333H4.55556V4.55556H15.4444V12.3333ZM15.4444 3H4.55556C3.69222 3 3 3.7 3 4.55556V15.4444C3 15.857 3.16389 16.2527 3.45561 16.5444C3.74733 16.8361 4.143 17 4.55556 17H15.4444C15.857 17 16.2527 16.8361 16.5444 16.5444C16.8361 16.2527 17 15.857 17 15.4444V4.55556C17 4.143 16.8361 3.74733 16.5444 3.45561C16.2527 3.16389 15.857 3 15.4444 3Z" />
|
||||
<path d="M13.1111 8.44442H11.5556V6.11108H8.44447V8.44442H6.88892L10 11.5555L13.1111 8.44442Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 646 B |
3
app/assets/icons/ic-more.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3333 9.99992C13.3333 9.55789 13.5088 9.13397 13.8214 8.82141C14.134 8.50885 14.5579 8.33325 14.9999 8.33325C15.4419 8.33325 15.8659 8.50885 16.1784 8.82141C16.491 9.13397 16.6666 9.55789 16.6666 9.99992C16.6666 10.4419 16.491 10.8659 16.1784 11.1784C15.8659 11.491 15.4419 11.6666 14.9999 11.6666C14.5579 11.6666 14.134 11.491 13.8214 11.1784C13.5088 10.8659 13.3333 10.4419 13.3333 9.99992ZM8.33325 9.99992C8.33325 9.55789 8.50885 9.13397 8.82141 8.82141C9.13397 8.50885 9.55789 8.33325 9.99992 8.33325C10.4419 8.33325 10.8659 8.50885 11.1784 8.82141C11.491 9.13397 11.6666 9.55789 11.6666 9.99992C11.6666 10.4419 11.491 10.8659 11.1784 11.1784C10.8659 11.491 10.4419 11.6666 9.99992 11.6666C9.55789 11.6666 9.13397 11.491 8.82141 11.1784C8.50885 10.8659 8.33325 10.4419 8.33325 9.99992ZM3.33325 9.99992C3.33325 9.55789 3.50885 9.13397 3.82141 8.82141C4.13397 8.50885 4.55789 8.33325 4.99992 8.33325C5.44195 8.33325 5.86587 8.50885 6.17843 8.82141C6.49099 9.13397 6.66658 9.55789 6.66658 9.99992C6.66658 10.4419 6.49099 10.8659 6.17843 11.1784C5.86587 11.491 5.44195 11.6666 4.99992 11.6666C4.55789 11.6666 4.13397 11.491 3.82141 11.1784C3.50885 10.8659 3.33325 10.4419 3.33325 9.99992Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
app/assets/icons/ic-pencil-off.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.25 7.9L12.25 4.9L15.1 7.75L12.1 10.75L11.05 9.7L13 7.675L12.325 7L10.375 8.95L9.25 7.9ZM17.275 4.45L15.55 2.725C15.4 2.575 15.175 2.5 15.025 2.5C14.875 2.5 14.65 2.575 14.5 2.725L13.15 4.075L16 6.925L17.275 5.5C17.575 5.275 17.575 4.75 17.275 4.45ZM16 16.525L15.025 17.5L10.15 12.625L6.85 16H4V13.15L7.375 9.775L2.5 4.975L3.475 4L16 16.525ZM9.1 11.575L8.425 10.9L5.5 13.825V14.5H6.175L9.1 11.575Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
3
app/assets/icons/ic-pin-off.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 9.68991L1.66675 4.35665L2.84526 3.17813L16.9874 17.3203L15.8089 18.4988L10.6 13.2899V17.5H9.4V13H4.50001V11.5L7 10V9.68991ZM8.8101 11.5H6.60001L8.09971 10.7896L8.8101 11.5ZM11.5 4V9.47623L15.0238 13H15.5V11.5L13 10V4H13.75V2.5H6.25001V4H7V4.97623L8.5 6.47623V4H11.5Z" fill="#72767E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 440 B |
3
app/assets/icons/ic-pin.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 10V4H13.75V2.5H6.25V4H7V10L4.5 11.5V13H9.4V17.5H10.6V13H15.5V11.5L13 10ZM6.6 11.5L8.5 10.6V4H11.5V10.6L13.4 11.5H6.6Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 238 B |
3
app/assets/icons/ic-text-rich.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 7.49984H7.5V12.4998H2.5V7.49984ZM2.5 4.1665H17.5V5.83317H2.5V4.1665ZM17.5 7.49984V9.1665H9.16667V7.49984H17.5ZM17.5 10.8332V12.4998H9.16667V10.8332H17.5ZM2.5 14.1665H14.1667V15.8332H2.5V14.1665Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
3
app/assets/icons/ic-trash.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.49992 2.5V3.33333H3.33325V5H4.16659V15.8333C4.16659 16.2754 4.34218 16.6993 4.65474 17.0118C4.9673 17.3244 5.39122 17.5 5.83325 17.5H14.1666C14.6086 17.5 15.0325 17.3244 15.3451 17.0118C15.6577 16.6993 15.8333 16.2754 15.8333 15.8333V5H16.6666V3.33333H12.4999V2.5H7.49992ZM5.83325 5H14.1666V15.8333H5.83325V5ZM7.49992 6.66667V14.1667H9.16658V6.66667H7.49992ZM10.8333 6.66667V14.1667H12.4999V6.66667H10.8333Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
3
app/assets/icons/ic-unarchive.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.3333 12.3333H15.4444V4.55556H4.55556V12.3333H7.66667C7.66667 12.9522 7.9125 13.5457 8.35008 13.9832C8.78767 14.4208 9.38116 14.6667 10 14.6667C10.6188 14.6667 11.2123 14.4208 11.6499 13.9832C12.0875 13.5457 12.3333 12.9522 12.3333 12.3333ZM4.55556 3H15.4444C15.857 3 16.2527 3.16389 16.5444 3.45561C16.8361 3.74733 17 4.143 17 4.55556V15.4444C17 15.857 16.8361 16.2527 16.5444 16.5444C16.2527 16.8361 15.857 17 15.4444 17H4.55556C4.143 17 3.74733 16.8361 3.45561 16.5444C3.16389 16.2527 3 15.857 3 15.4444V4.55556C3 3.7 3.69222 3 4.55556 3ZM8.44442 8.91667L6.88886 8.91667L9.99997 5.80555L13.1111 8.91667H11.5555V11.25H8.44442V8.91667Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 796 B |
35
app/assets/icons/il-notes.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="60" cy="60" r="60" fill="#F4F5F7"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<rect x="21.5051" y="49.3528" width="40" height="40" rx="4" transform="rotate(-15 21.5051 49.3528)" fill="white"/>
|
||||
</g>
|
||||
<rect x="29.8889" y="57.4591" width="28" height="4" rx="2" transform="rotate(-15 29.8889 57.4591)" fill="#BBBEC4"/>
|
||||
<rect x="31.9595" y="65.1865" width="28" height="4" rx="2" transform="rotate(-15 31.9595 65.1865)" fill="#BBBEC4"/>
|
||||
<rect x="34.03" y="72.9139" width="16" height="4" rx="2" transform="rotate(-15 34.03 72.9139)" fill="#BBBEC4"/>
|
||||
<g filter="url(#filter1_d)">
|
||||
<rect x="40" y="32" width="56" height="56" rx="4" fill="white"/>
|
||||
</g>
|
||||
<rect x="48" y="46" width="40" height="6" rx="3" fill="#BBBEC4"/>
|
||||
<rect x="48" y="57" width="40" height="6" rx="3" fill="#BBBEC4"/>
|
||||
<rect x="48" y="68" width="22" height="6" rx="3" fill="#BBBEC4"/>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="10.4031" y="31.8979" width="71.1938" height="71.1938" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="28" y="24" width="80" height="80" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -59,6 +59,7 @@ import { SessionsModalDirective } from './components/SessionsModal';
|
||||
import { NoAccountWarningDirective } from './components/NoAccountWarning';
|
||||
import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning';
|
||||
import { SearchOptionsDirective } from './components/SearchOptions';
|
||||
import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNotes';
|
||||
|
||||
function reloadHiddenFirefoxTab(): boolean {
|
||||
/**
|
||||
@@ -147,7 +148,8 @@ const startApplication: StartApplication = async function startApplication(
|
||||
.directive('sessionsModal', SessionsModalDirective)
|
||||
.directive('noAccountWarning', NoAccountWarningDirective)
|
||||
.directive('protectedNotePanel', NoProtectionsdNoteWarningDirective)
|
||||
.directive('searchOptions', SearchOptionsDirective);
|
||||
.directive('searchOptions', SearchOptionsDirective)
|
||||
.directive('multipleSelectedNotesPanel', MultipleSelectedNotesDirective);
|
||||
|
||||
// Filters
|
||||
angular.module('app').filter('trusted', ['$sce', trusted]);
|
||||
|
||||
192
app/assets/javascripts/components/MultipleSelectedNotes.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import VisuallyHidden from '@reach/visually-hidden';
|
||||
import { toDirective, useCloseOnBlur } from './utils';
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
} from '@reach/disclosure';
|
||||
import MoreIcon from '../../icons/ic-more.svg';
|
||||
import PencilOffIcon from '../../icons/ic-pencil-off.svg';
|
||||
import RichTextIcon from '../../icons/ic-text-rich.svg';
|
||||
import TrashIcon from '../../icons/ic-trash.svg';
|
||||
import PinIcon from '../../icons/ic-pin.svg';
|
||||
import UnpinIcon from '../../icons/ic-pin-off.svg';
|
||||
import ArchiveIcon from '../../icons/ic-archive.svg';
|
||||
import UnarchiveIcon from '../../icons/ic-unarchive.svg';
|
||||
import NotesIcon from '../../icons/il-notes.svg';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import { Switch } from './Switch';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { SNApplication } from '@standardnotes/snjs';
|
||||
|
||||
type Props = {
|
||||
application: SNApplication;
|
||||
appState: AppState;
|
||||
};
|
||||
|
||||
const MultipleSelectedNotes = observer(({ appState }: Props) => {
|
||||
const count = appState.notes.selectedNotesCount;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [optionsPanelPosition, setOptionsPanelPosition] = useState({
|
||||
top: 0,
|
||||
right: 0,
|
||||
});
|
||||
const buttonRef = useRef<HTMLButtonElement>();
|
||||
const panelRef = useRef<HTMLDivElement>();
|
||||
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef, setOpen);
|
||||
|
||||
const notes = Object.values(appState.notes.selectedNotes);
|
||||
const hidePreviews = !notes.some((note) => !note.hidePreview);
|
||||
const locked = !notes.some((note) => !note.locked);
|
||||
const archived = !notes.some((note) => !note.archived);
|
||||
const trashed = !notes.some((note) => !note.trashed);
|
||||
const pinned = !notes.some((note) => !note.pinned);
|
||||
|
||||
const iconClass = 'fill-current color-neutral mr-2.5';
|
||||
const buttonClass =
|
||||
'flex items-center border-0 capitalize focus:inner-ring-info ' +
|
||||
'cursor-pointer hover:bg-contrast color-text bg-transparent h-10 px-3 ' +
|
||||
'text-left';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full items-center">
|
||||
<div className="flex items-center justify-between p-4 w-full">
|
||||
<h1 className="text-3xl m-0">{count} selected notes</h1>
|
||||
<Disclosure
|
||||
open={open}
|
||||
onChange={() => {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
setOptionsPanelPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
});
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
}}
|
||||
>
|
||||
<DisclosureButton
|
||||
onKeyUp={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className={
|
||||
'bg-transparent border-solid border-1 border-gray-300 ' +
|
||||
'cursor-pointer w-32px h-32px rounded-full p-0 ' +
|
||||
'flex justify-center items-center'
|
||||
}
|
||||
>
|
||||
<VisuallyHidden>Actions</VisuallyHidden>
|
||||
<MoreIcon className="fill-current block" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyUp={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false);
|
||||
buttonRef.current.focus();
|
||||
}
|
||||
}}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...optionsPanelPosition,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown-anchor-right flex flex-col py-2 select-none"
|
||||
>
|
||||
<Switch
|
||||
onBlur={closeOnBlur}
|
||||
className="h-10"
|
||||
checked={locked}
|
||||
onChange={() => {
|
||||
appState.notes.setLockSelectedNotes(!locked);
|
||||
}}
|
||||
>
|
||||
<span className="capitalize flex items-center">
|
||||
<PencilOffIcon className={iconClass} />
|
||||
Prevent editing
|
||||
</span>
|
||||
</Switch>
|
||||
<Switch
|
||||
onBlur={closeOnBlur}
|
||||
className="h-10"
|
||||
checked={!hidePreviews}
|
||||
onChange={() => {
|
||||
appState.notes.setHideSelectedNotePreviews(!hidePreviews);
|
||||
}}
|
||||
>
|
||||
<span className="capitalize flex items-center">
|
||||
<RichTextIcon className={iconClass} />
|
||||
Show Preview
|
||||
</span>
|
||||
</Switch>
|
||||
<div className="h-1px my-2.5 bg-secondary-contrast"></div>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className={buttonClass}
|
||||
onClick={() => {
|
||||
appState.notes.setPinSelectedNotes(!pinned);
|
||||
}}
|
||||
>
|
||||
{pinned ? (
|
||||
<>
|
||||
<UnpinIcon className={iconClass} />
|
||||
Unpin notes
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PinIcon className={iconClass} />
|
||||
Pin notes
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className={buttonClass}
|
||||
onClick={() => {
|
||||
appState.notes.setArchiveSelectedNotes(!archived);
|
||||
}}
|
||||
>
|
||||
{archived ? (
|
||||
<>
|
||||
<UnarchiveIcon className={iconClass} />
|
||||
Unarchive
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArchiveIcon className={iconClass} />
|
||||
Archive
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className={buttonClass}
|
||||
onClick={async () => {
|
||||
setLockCloseOnBlur(true);
|
||||
await appState.notes.setTrashSelectedNotes(!trashed);
|
||||
setLockCloseOnBlur(false);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className={iconClass} />
|
||||
{trashed ? 'Restore' : 'Move to trash'}
|
||||
</button>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
|
||||
<NotesIcon className="block" />
|
||||
<h2 className="text-2xl m-0 text-center mt-4">
|
||||
{count} selected notes
|
||||
</h2>
|
||||
<p className="text-lg mt-2 text-center">
|
||||
Actions will be performed on all selected notes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const MultipleSelectedNotesDirective = toDirective<Props>(
|
||||
MultipleSelectedNotes
|
||||
);
|
||||
@@ -1,13 +1,12 @@
|
||||
import { toDirective, useAutorunValue } from './utils';
|
||||
import Close from '../../icons/ic_close.svg';
|
||||
import { toDirective } from './utils';
|
||||
import Close from '../../icons/ic-close.svg';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
type Props = { appState: AppState };
|
||||
|
||||
function NoAccountWarning({ appState }: Props) {
|
||||
const canShow = useAutorunValue(() => appState.noAccountWarning.show, [
|
||||
appState,
|
||||
]);
|
||||
const NoAccountWarning = observer(({ appState }: Props) => {
|
||||
const canShow = appState.noAccountWarning.show;
|
||||
if (!canShow) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,6 +38,6 @@ function NoAccountWarning({ appState }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const NoAccountWarningDirective = toDirective<Props>(NoAccountWarning);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { toDirective, useAutorunValue } from './utils';
|
||||
import { toDirective, useCloseOnBlur } from './utils';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import VisuallyHidden from '@reach/visually-hidden';
|
||||
@@ -10,54 +10,35 @@ import {
|
||||
} from '@reach/disclosure';
|
||||
import { FocusEvent } from 'react';
|
||||
import { Switch } from './Switch';
|
||||
import TuneIcon from '../../icons/ic_tune.svg';
|
||||
import TuneIcon from '../../icons/ic-tune.svg';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
function SearchOptions({ appState }: Props) {
|
||||
const SearchOptions = observer(({ appState }: Props) => {
|
||||
const { searchOptions } = appState;
|
||||
|
||||
const {
|
||||
includeProtectedContents,
|
||||
includeArchived,
|
||||
includeTrashed,
|
||||
} = useAutorunValue(
|
||||
() => ({
|
||||
includeProtectedContents: searchOptions.includeProtectedContents,
|
||||
includeArchived: searchOptions.includeArchived,
|
||||
includeTrashed: searchOptions.includeTrashed,
|
||||
}),
|
||||
[searchOptions]
|
||||
);
|
||||
|
||||
const [
|
||||
togglingIncludeProtectedContents,
|
||||
setTogglingIncludeProtectedContents,
|
||||
] = useState(false);
|
||||
|
||||
async function toggleIncludeProtectedContents() {
|
||||
setTogglingIncludeProtectedContents(true);
|
||||
try {
|
||||
await searchOptions.toggleIncludeProtectedContents();
|
||||
} finally {
|
||||
setTogglingIncludeProtectedContents(false);
|
||||
}
|
||||
}
|
||||
} = searchOptions;
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [optionsPanelTop, setOptionsPanelTop] = useState(0);
|
||||
const buttonRef = useRef<HTMLButtonElement>();
|
||||
const panelRef = useRef<HTMLDivElement>();
|
||||
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef, setOpen);
|
||||
|
||||
function closeOnBlur(event: FocusEvent<HTMLElement>) {
|
||||
if (
|
||||
!togglingIncludeProtectedContents &&
|
||||
!panelRef.current.contains(event.relatedTarget as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
async function toggleIncludeProtectedContents() {
|
||||
setLockCloseOnBlur(true);
|
||||
try {
|
||||
await searchOptions.toggleIncludeProtectedContents();
|
||||
} finally {
|
||||
setLockCloseOnBlur(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +67,7 @@ function SearchOptions({ appState }: Props) {
|
||||
className="sn-dropdown sn-dropdown-anchor-right grid gap-2 py-2"
|
||||
>
|
||||
<Switch
|
||||
className="h-10"
|
||||
checked={includeProtectedContents}
|
||||
onChange={toggleIncludeProtectedContents}
|
||||
onBlur={closeOnBlur}
|
||||
@@ -93,6 +75,7 @@ function SearchOptions({ appState }: Props) {
|
||||
<p className="capitalize">Include protected contents</p>
|
||||
</Switch>
|
||||
<Switch
|
||||
className="h-10"
|
||||
checked={includeArchived}
|
||||
onChange={searchOptions.toggleIncludeArchived}
|
||||
onBlur={closeOnBlur}
|
||||
@@ -100,6 +83,7 @@ function SearchOptions({ appState }: Props) {
|
||||
<p className="capitalize">Include archived notes</p>
|
||||
</Switch>
|
||||
<Switch
|
||||
className="h-10"
|
||||
checked={includeTrashed}
|
||||
onChange={searchOptions.toggleIncludeTrashed}
|
||||
onBlur={closeOnBlur}
|
||||
@@ -109,6 +93,6 @@ function SearchOptions({ appState }: Props) {
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const SearchOptionsDirective = toDirective<Props>(SearchOptions);
|
||||
|
||||
@@ -15,8 +15,9 @@ import {
|
||||
AlertDialogDescription,
|
||||
AlertDialogLabel,
|
||||
} from '@reach/alert-dialog';
|
||||
import { toDirective, useAutorunValue } from './utils';
|
||||
import { toDirective } from './utils';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
type Session = RemoteSession & {
|
||||
revoking?: true;
|
||||
@@ -242,16 +243,12 @@ const SessionsModal: FunctionComponent<{
|
||||
const Sessions: FunctionComponent<{
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
}> = ({ appState, application }) => {
|
||||
const showModal = useAutorunValue(() => appState.isSessionsModalVisible, [
|
||||
appState,
|
||||
]);
|
||||
|
||||
if (showModal) {
|
||||
}> = observer(({ appState, application }) => {
|
||||
if (appState.isSessionsModalVisible) {
|
||||
return <SessionsModal application={application} appState={appState} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const SessionsModalDirective = toDirective(Sessions);
|
||||
|
||||
@@ -11,6 +11,7 @@ import '@reach/checkbox/styles.css';
|
||||
export type SwitchProps = HTMLProps<HTMLInputElement> & {
|
||||
checked?: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
className?: string;
|
||||
children: ComponentChildren;
|
||||
};
|
||||
|
||||
@@ -19,8 +20,9 @@ export const Switch: FunctionalComponent<SwitchProps> = (
|
||||
) => {
|
||||
const [checkedState, setChecked] = useState(props.checked || false);
|
||||
const checked = props.checked ?? checkedState;
|
||||
const className = props.className ?? '';
|
||||
return (
|
||||
<label className="sn-component flex justify-between items-center cursor-pointer hover:bg-contrast py-2 px-3">
|
||||
<label className={`sn-component flex justify-between items-center cursor-pointer hover:bg-contrast px-3 ${className}`}>
|
||||
{props.children}
|
||||
<CustomCheckboxContainer
|
||||
checked={checked}
|
||||
@@ -33,6 +35,7 @@ export const Switch: FunctionalComponent<SwitchProps> = (
|
||||
<CustomCheckboxInput
|
||||
{...({
|
||||
...props,
|
||||
className: undefined,
|
||||
children: undefined,
|
||||
} as CustomCheckboxInputProps)}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
import { autorun } from 'mobx';
|
||||
import { FunctionComponent, h, render } from 'preact';
|
||||
import { Inputs, useEffect, useState } from 'preact/hooks';
|
||||
import { StateUpdater, useCallback, useState } from 'preact/hooks';
|
||||
import { FocusEvent, EventHandler, FocusEventHandler } from 'react';
|
||||
|
||||
export function useAutorunValue<T>(query: () => T, inputs: Inputs): T {
|
||||
const [value, setValue] = useState(query);
|
||||
useEffect(() => {
|
||||
return autorun(() => {
|
||||
setValue(query());
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, inputs);
|
||||
return value;
|
||||
/**
|
||||
* @returns a callback that will close a dropdown if none of its children has
|
||||
* focus. Must be set as the onBlur callback of children that need to be
|
||||
* monitored.
|
||||
*/
|
||||
export function useCloseOnBlur(
|
||||
container: { current: HTMLDivElement },
|
||||
setOpen: (open: boolean) => void
|
||||
): [(event: { relatedTarget: EventTarget | null }) => void, StateUpdater<boolean>] {
|
||||
const [locked, setLocked] = useState(false);
|
||||
return [
|
||||
useCallback(
|
||||
function onBlur(event: { relatedTarget: EventTarget | null }) {
|
||||
if (
|
||||
!locked &&
|
||||
!container.current.contains(event.relatedTarget as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
},
|
||||
[container, setOpen, locked]
|
||||
),
|
||||
setLocked,
|
||||
];
|
||||
}
|
||||
|
||||
export function toDirective<Props>(
|
||||
|
||||
@@ -25,10 +25,8 @@ import {
|
||||
ApplicationEvent,
|
||||
BackupFile,
|
||||
ContentType,
|
||||
Platform,
|
||||
} from '@standardnotes/snjs';
|
||||
import { confirmDialog, alertDialog } from '@/services/alertService';
|
||||
import { autorun, IReactionDisposer } from 'mobx';
|
||||
import { storage, StorageKey } from '@/services/localStorage';
|
||||
import {
|
||||
disableErrorReporting,
|
||||
@@ -84,8 +82,6 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
|
||||
public appVersion: string;
|
||||
/** @template */
|
||||
private closeFunction?: () => void;
|
||||
private removeBetaWarningListener?: IReactionDisposer;
|
||||
private removeSyncObserver?: IReactionDisposer;
|
||||
private removeProtectionLengthObserver?: () => void;
|
||||
|
||||
/* @ngInject */
|
||||
@@ -152,13 +148,13 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
|
||||
});
|
||||
|
||||
const sync = this.appState.sync;
|
||||
this.removeSyncObserver = autorun(() => {
|
||||
this.autorun(() => {
|
||||
this.setState({
|
||||
syncInProgress: sync.inProgress,
|
||||
syncError: sync.errorMessage,
|
||||
});
|
||||
});
|
||||
this.removeBetaWarningListener = autorun(() => {
|
||||
this.autorun(() => {
|
||||
this.setState({
|
||||
showBetaWarning: this.appState.showBetaWarning,
|
||||
});
|
||||
@@ -175,8 +171,6 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.removeSyncObserver?.();
|
||||
this.removeBetaWarningListener?.();
|
||||
this.removeProtectionLengthObserver?.();
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { SNItem, Action, SNActionsExtension, UuidString } from '@standardnotes/snjs';
|
||||
import { ActionResponse } from '@standardnotes/snjs';
|
||||
import { ActionsExtensionMutator } from '@standardnotes/snjs';
|
||||
import { autorun, IReactionDisposer } from 'mobx';
|
||||
|
||||
type ActionsMenuScope = {
|
||||
application: WebApplication
|
||||
@@ -43,7 +42,6 @@ type ActionsMenuState = {
|
||||
class ActionsMenuCtrl extends PureViewCtrl<unknown, ActionsMenuState> implements ActionsMenuScope {
|
||||
application!: WebApplication
|
||||
item!: SNItem
|
||||
private removeHiddenExtensionsListener?: IReactionDisposer;
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
@@ -58,17 +56,13 @@ class ActionsMenuCtrl extends PureViewCtrl<unknown, ActionsMenuState> implements
|
||||
item: this.item
|
||||
});
|
||||
this.loadExtensions();
|
||||
this.removeHiddenExtensionsListener = autorun(() => {
|
||||
this.autorun(() => {
|
||||
this.rebuildMenu({
|
||||
hiddenExtensions: this.appState.actionsMenu.hiddenExtensions
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.removeHiddenExtensionsListener?.();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getInitialState() {
|
||||
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => {
|
||||
|
||||
@@ -125,5 +125,7 @@ export const Strings = {
|
||||
return `Your keys are currently stored in your operating system's ${keychainName}. Adding a passcode prevents even your operating system from reading them.`;
|
||||
},
|
||||
protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.',
|
||||
openAccountMenu: 'Open Account Menu'
|
||||
openAccountMenu: 'Open Account Menu',
|
||||
trashNotesTitle: 'Move To Trash',
|
||||
trashNotesText: 'Are you sure you want to move these notes to the trash?'
|
||||
};
|
||||
|
||||
@@ -83,7 +83,8 @@ export class AppState {
|
||||
this.application,
|
||||
async () => {
|
||||
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
|
||||
}
|
||||
},
|
||||
this.appEventObserverRemovers,
|
||||
);
|
||||
this.noAccountWarning = new NoAccountWarningState(
|
||||
application,
|
||||
|
||||
@@ -1,41 +1,81 @@
|
||||
import { KeyboardModifier } from "@/services/ioService";
|
||||
import { UuidString, SNNote } from "@standardnotes/snjs";
|
||||
import { makeObservable, observable, action } from "mobx";
|
||||
import { WebApplication } from "../application";
|
||||
import { Editor } from "../editor";
|
||||
import { confirmDialog } from '@/services/alertService';
|
||||
import { KeyboardModifier } from '@/services/ioService';
|
||||
import { Strings } from '@/strings';
|
||||
import {
|
||||
UuidString,
|
||||
SNNote,
|
||||
NoteMutator,
|
||||
ContentType,
|
||||
} from '@standardnotes/snjs';
|
||||
import {
|
||||
makeObservable,
|
||||
observable,
|
||||
action,
|
||||
computed,
|
||||
runInAction,
|
||||
} from 'mobx';
|
||||
import { WebApplication } from '../application';
|
||||
import { Editor } from '../editor';
|
||||
|
||||
export class NotesState {
|
||||
selectedNotes: Record<UuidString, SNNote> = {};
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
private onActiveEditorChanged: () => Promise<void>
|
||||
private onActiveEditorChanged: () => Promise<void>,
|
||||
appEventListeners: (() => void)[]
|
||||
) {
|
||||
makeObservable(this, {
|
||||
selectedNotes: observable,
|
||||
|
||||
selectedNotesCount: computed,
|
||||
|
||||
selectNote: action,
|
||||
setHideSelectedNotePreviews: action,
|
||||
setLockSelectedNotes: action,
|
||||
});
|
||||
|
||||
appEventListeners.push(
|
||||
application.streamItems(ContentType.Note, (notes) => {
|
||||
runInAction(() => {
|
||||
for (const note of notes) {
|
||||
if (this.selectedNotes[note.uuid]) {
|
||||
this.selectedNotes[note.uuid] = note as SNNote;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get activeEditor(): Editor | undefined {
|
||||
return this.application.editorGroup.editors[0];
|
||||
}
|
||||
|
||||
async selectNote(note: SNNote): Promise<void> {
|
||||
get selectedNotesCount(): number {
|
||||
return Object.keys(this.selectedNotes).length;
|
||||
}
|
||||
|
||||
async selectNote(uuid: UuidString): Promise<void> {
|
||||
const note = this.application.findItem(uuid) as SNNote;
|
||||
if (
|
||||
this.io.activeModifiers.has(KeyboardModifier.Meta) ||
|
||||
this.io.activeModifiers.has(KeyboardModifier.Ctrl)
|
||||
) {
|
||||
this.selectedNotes[note.uuid] = note;
|
||||
if (this.selectedNotes[uuid]) {
|
||||
delete this.selectedNotes[uuid];
|
||||
} else {
|
||||
this.selectedNotes[uuid] = note;
|
||||
}
|
||||
} else {
|
||||
this.selectedNotes = {
|
||||
[note.uuid]: note,
|
||||
[uuid]: note,
|
||||
};
|
||||
await this.openEditor(uuid);
|
||||
}
|
||||
await this.openEditor(note.uuid);
|
||||
}
|
||||
|
||||
async openEditor(noteUuid: string): Promise<void> {
|
||||
private async openEditor(noteUuid: string): Promise<void> {
|
||||
if (this.activeEditor?.note?.uuid === noteUuid) {
|
||||
return;
|
||||
}
|
||||
@@ -60,6 +100,69 @@ export class NotesState {
|
||||
}
|
||||
}
|
||||
|
||||
setHideSelectedNotePreviews(hide: boolean): void {
|
||||
this.application.changeItems<NoteMutator>(
|
||||
Object.keys(this.selectedNotes),
|
||||
(mutator) => {
|
||||
mutator.hidePreview = hide;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
setLockSelectedNotes(lock: boolean): void {
|
||||
this.application.changeItems<NoteMutator>(
|
||||
Object.keys(this.selectedNotes),
|
||||
(mutator) => {
|
||||
mutator.locked = lock;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
async setTrashSelectedNotes(trashed: boolean): Promise<void> {
|
||||
if (
|
||||
await confirmDialog({
|
||||
title: Strings.trashNotesTitle,
|
||||
text: Strings.trashNotesText,
|
||||
confirmButtonStyle: 'danger',
|
||||
})
|
||||
) {
|
||||
this.application.changeItems<NoteMutator>(
|
||||
Object.keys(this.selectedNotes),
|
||||
(mutator) => {
|
||||
mutator.trashed = trashed;
|
||||
},
|
||||
false
|
||||
);
|
||||
runInAction(() => {
|
||||
this.selectedNotes = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setPinSelectedNotes(pinned: boolean): void {
|
||||
this.application.changeItems<NoteMutator>(
|
||||
Object.keys(this.selectedNotes),
|
||||
(mutator) => {
|
||||
mutator.pinned = pinned;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
setArchiveSelectedNotes(archived: boolean): void {
|
||||
this.application.changeItems<NoteMutator>(
|
||||
Object.keys(this.selectedNotes),
|
||||
(mutator) => {
|
||||
mutator.archived = archived;
|
||||
}
|
||||
);
|
||||
runInAction(() => {
|
||||
this.selectedNotes = {};
|
||||
});
|
||||
}
|
||||
|
||||
private get io() {
|
||||
return this.application.io;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
)
|
||||
tags-view(application='self.application')
|
||||
notes-view(application='self.application')
|
||||
editor-group-view(
|
||||
application='self.application'
|
||||
)
|
||||
editor-group-view.flex-grow(application='self.application')
|
||||
|
||||
footer-view(
|
||||
ng-if='!self.state.needsUnlock && self.state.ready'
|
||||
|
||||
@@ -1185,7 +1185,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
* (and not when our controller is destroyed.)
|
||||
*/
|
||||
angular.element(editor).one('$destroy', () => {
|
||||
this.removeTabObserver();
|
||||
this.removeTabObserver?.();
|
||||
this.removeTabObserver = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
.flex-grow(
|
||||
ng-repeat='editor in self.editors'
|
||||
)
|
||||
editor-view(
|
||||
.h-full
|
||||
multiple-selected-notes-panel.h-full(
|
||||
app-state='self.appState'
|
||||
application='self.application'
|
||||
editor='editor'
|
||||
ng-if='self.state.showMultipleSelectedNotes'
|
||||
)
|
||||
.flex-grow.h-full(
|
||||
ng-if='!self.state.showMultipleSelectedNotes'
|
||||
ng-repeat='editor in self.editors'
|
||||
)
|
||||
editor-view(
|
||||
application='self.application'
|
||||
editor='editor'
|
||||
)
|
||||
|
||||
@@ -2,16 +2,31 @@ import { WebApplication } from '@/ui_models/application';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from './editor-group-view.pug';
|
||||
import { Editor } from '@/ui_models/editor';
|
||||
import { PureViewCtrl } from '../abstract/pure_view_ctrl';
|
||||
|
||||
class EditorGroupViewCtrl {
|
||||
class EditorGroupViewCtrl extends PureViewCtrl<unknown, {
|
||||
showMultipleSelectedNotes: boolean
|
||||
}> {
|
||||
|
||||
private application!: WebApplication
|
||||
public editors: Editor[] = []
|
||||
|
||||
/* @ngInject */
|
||||
constructor($timeout: ng.ITimeoutService,) {
|
||||
super($timeout);
|
||||
this.state = {
|
||||
showMultipleSelectedNotes: false
|
||||
}
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.application.editorGroup.addChangeObserver(() => {
|
||||
this.editors = this.application.editorGroup.editors;
|
||||
});
|
||||
this.autorun(() => {
|
||||
this.setState({
|
||||
showMultipleSelectedNotes: this.appState.notes.selectedNotesCount > 1
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +35,6 @@ export class EditorGroupView extends WebDirective {
|
||||
super();
|
||||
this.template = template;
|
||||
this.controller = EditorGroupViewCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
} from '@/strings';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { alertDialog, confirmDialog } from '@/services/alertService';
|
||||
import { autorun, IReactionDisposer } from 'mobx';
|
||||
|
||||
/**
|
||||
* Disable before production release.
|
||||
@@ -75,7 +74,6 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
|
||||
private observerRemovers: Array<() => void> = [];
|
||||
private completedInitialSync = false;
|
||||
private showingDownloadStatus = false;
|
||||
private autorunDisposer?: IReactionDisposer;
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
@@ -103,7 +101,6 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
|
||||
this.rootScopeListener2 = undefined;
|
||||
(this.closeAccountMenu as any) = undefined;
|
||||
(this.toggleSyncResolutionMenu as any) = undefined;
|
||||
this.autorunDisposer?.();
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
@@ -115,7 +112,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
|
||||
});
|
||||
});
|
||||
this.loadAccountSwitcherState();
|
||||
this.autorunDisposer = autorun(() => {
|
||||
this.autorun(() => {
|
||||
const showBetaWarning = this.appState.showBetaWarning;
|
||||
this.showAccountMenu = this.appState.accountMenu.show;
|
||||
this.setState({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#notes-column.sn-component.section.notes(aria-label='Notes')
|
||||
.content
|
||||
#notes-title-bar.section-title-bar
|
||||
.p-4.pt-0
|
||||
.p-4
|
||||
.section-title-bar-header
|
||||
.sk-h2.font-semibold.title {{self.state.panelTitle}}
|
||||
.sk-button.contrast.wide(
|
||||
|
||||
@@ -297,7 +297,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
|
||||
}
|
||||
|
||||
selectNote(note: SNNote): Promise<void> {
|
||||
return this.appState.notes.selectNote(note);
|
||||
return this.appState.notes.selectNote(note.uuid);
|
||||
}
|
||||
|
||||
async createNewNote() {
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
}
|
||||
|
||||
#notes-title-bar {
|
||||
padding-top: 16px;
|
||||
font-weight: normal;
|
||||
|
||||
.section-title-bar-header .title {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/* Components and utilities that are good candidates for extraction to StyleKit. */
|
||||
|
||||
.outline-none {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
@@ -12,22 +8,59 @@
|
||||
border-color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
.focus-within\:border-background:focus-within {
|
||||
border-color: var(--sn-stylekit-background-color);
|
||||
.border-transparent {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.border-transparent {
|
||||
border-color: var(--sn-stylekit-background-color);
|
||||
.border-info {
|
||||
border-color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.bg-clip-padding {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.bg-secondary-contrast {
|
||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.focus-within\:border-background:focus-within {
|
||||
border-color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.mr-2\.5 {
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
|
||||
.my-2\.5 {
|
||||
margin-top: 0.625rem;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.outline-none {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ring-info {
|
||||
box-shadow: 0 0 0 2px var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.inner-ring-info {
|
||||
box-shadow: inset 0 0 0 2px var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.focus\:inner-ring-info:focus {
|
||||
@extend .inner-ring-info;
|
||||
}
|
||||
|
||||
.focus\:ring-info:focus {
|
||||
@extend .ring-info;
|
||||
}
|
||||
@@ -36,8 +69,29 @@
|
||||
@extend .ring-info;
|
||||
}
|
||||
|
||||
.border-transparent {
|
||||
border-color: transparent;
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
.w-32px {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.h-32px {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.h-1px {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +117,6 @@
|
||||
@extend .bg-default;
|
||||
@extend .min-w-80;
|
||||
@extend .duration-150;
|
||||
@extend .grid;
|
||||
@extend .gap-2;
|
||||
@extend .slide-down-animation;
|
||||
@extend .rounded;
|
||||
@extend .box-shadow;
|
||||
|
||||
@@ -109,3 +109,7 @@ input:focus {
|
||||
box-shadow: 0 0 0 2px var(--sn-stylekit-background-color),
|
||||
0 0 0 4px var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.sk-button:focus-visible, button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -209,18 +209,9 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.grid-template-cols-1fr {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.col-span-all {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.grid-col-2 {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"@standardnotes/sncrypto-web": "^1.2.10",
|
||||
"@standardnotes/snjs": "^2.0.74",
|
||||
"mobx": "^6.1.6",
|
||||
"mobx-react-lite": "^3.2.0",
|
||||
"preact": "^10.5.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6094,6 +6094,11 @@ mixin-deep@^1.2.0:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mobx-react-lite@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f"
|
||||
integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==
|
||||
|
||||
mobx@^6.1.6:
|
||||
version "6.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.1.6.tgz#ae75e57ec07d190ed187273864002163fa357224"
|
||||
|
||||