From ed69680295d48986af21d8526f9de916d0e47b43 Mon Sep 17 00:00:00 2001 From: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:48:25 +0200 Subject: [PATCH] feat: search options (#540) * feat: search options * feat: sanitize folder names * fix: add cursor: pointer to switch * fix: explicitly make the search bar a text input * refactor: remove magic number * refactor: extract Switch component to its own file * refactor: split AppState into multiple files * refactor: review comments --- app/assets/icons/ic_tune.svg | 3 + app/assets/javascripts/app.ts | 4 +- .../javascripts/components/SearchOptions.tsx | 112 ++++++++ app/assets/javascripts/components/Switch.tsx | 48 ++++ app/assets/javascripts/components/utils.ts | 3 + app/assets/javascripts/index.ts | 1 - .../javascripts/services/archiveManager.ts | 40 +-- .../ui_models/app_state/account_menu_state.ts | 21 ++ .../ui_models/app_state/actions_menu_state.ts | 22 ++ .../ui_models/{ => app_state}/app_state.ts | 131 +-------- .../javascripts/ui_models/app_state/index.ts | 6 + .../app_state/no_account_warning_state.ts | 41 +++ .../app_state/search_options_state.ts | 59 +++++ .../ui_models/app_state/sync_state.ts | 36 +++ .../javascripts/views/notes/notes-view.pug | 20 +- .../javascripts/views/notes/notes_view.ts | 250 ++++++++---------- app/assets/stylesheets/_notes.scss | 5 +- app/assets/stylesheets/_sn.scss | 59 +++++ app/assets/stylesheets/_ui.scss | 3 - app/assets/stylesheets/index.css.scss | 2 + package.json | 7 +- webpack.dev.js | 2 +- webpack.prod.js | 12 +- yarn.lock | 104 +++++++- 24 files changed, 672 insertions(+), 319 deletions(-) create mode 100644 app/assets/icons/ic_tune.svg create mode 100644 app/assets/javascripts/components/SearchOptions.tsx create mode 100644 app/assets/javascripts/components/Switch.tsx create mode 100644 app/assets/javascripts/ui_models/app_state/account_menu_state.ts create mode 100644 app/assets/javascripts/ui_models/app_state/actions_menu_state.ts rename app/assets/javascripts/ui_models/{ => app_state}/app_state.ts (77%) create mode 100644 app/assets/javascripts/ui_models/app_state/index.ts create mode 100644 app/assets/javascripts/ui_models/app_state/no_account_warning_state.ts create mode 100644 app/assets/javascripts/ui_models/app_state/search_options_state.ts create mode 100644 app/assets/javascripts/ui_models/app_state/sync_state.ts create mode 100644 app/assets/stylesheets/_sn.scss diff --git a/app/assets/icons/ic_tune.svg b/app/assets/icons/ic_tune.svg new file mode 100644 index 000000000..fbf26572a --- /dev/null +++ b/app/assets/icons/ic_tune.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 7b7273056..a2bcbd446 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -58,6 +58,7 @@ import { Bridge } from './services/bridge'; import { SessionsModalDirective } from './components/SessionsModal'; import { NoAccountWarningDirective } from './components/NoAccountWarning'; import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning'; +import { SearchOptionsDirective } from './components/SearchOptions'; function reloadHiddenFirefoxTab(): boolean { /** @@ -145,7 +146,8 @@ const startApplication: StartApplication = async function startApplication( .directive('syncResolutionMenu', () => new SyncResolutionMenu()) .directive('sessionsModal', SessionsModalDirective) .directive('noAccountWarning', NoAccountWarningDirective) - .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective); + .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective) + .directive('searchOptions', SearchOptionsDirective); // Filters angular.module('app').filter('trusted', ['$sce', trusted]); diff --git a/app/assets/javascripts/components/SearchOptions.tsx b/app/assets/javascripts/components/SearchOptions.tsx new file mode 100644 index 000000000..08d880fd9 --- /dev/null +++ b/app/assets/javascripts/components/SearchOptions.tsx @@ -0,0 +1,112 @@ +import { AppState } from '@/ui_models/app_state'; +import { toDirective, useAutorunValue } from './utils'; +import { useRef, useState } from 'preact/hooks'; +import { WebApplication } from '@/ui_models/application'; +import VisuallyHidden from '@reach/visually-hidden'; +import { + Disclosure, + DisclosureButton, + DisclosurePanel, +} from '@reach/disclosure'; +import { FocusEvent } from 'react'; +import { Switch } from './Switch'; +import TuneIcon from '../../icons/ic_tune.svg'; + +type Props = { + appState: AppState; + application: WebApplication; +}; + +function SearchOptions({ appState }: Props) { + const { searchOptions } = appState; + + const { + includeProtectedContents, + includeArchived, + includeTrashed, + } = useAutorunValue(() => ({ + includeProtectedContents: searchOptions.includeProtectedContents, + includeArchived: searchOptions.includeArchived, + includeTrashed: searchOptions.includeTrashed, + })); + + const [ + togglingIncludeProtectedContents, + setTogglingIncludeProtectedContents, + ] = useState(false); + + async function toggleIncludeProtectedContents() { + setTogglingIncludeProtectedContents(true); + try { + await searchOptions.toggleIncludeProtectedContents(); + } finally { + setTogglingIncludeProtectedContents(false); + } + } + + const [open, setOpen] = useState(false); + const [optionsPanelTop, setOptionsPanelTop] = useState(0); + const buttonRef = useRef(); + const panelRef = useRef(); + + function closeOnBlur(event: FocusEvent) { + if ( + !togglingIncludeProtectedContents && + !panelRef.current.contains(event.relatedTarget as Node) + ) { + setOpen(false); + } + } + + return ( + { + const { height } = buttonRef.current.getBoundingClientRect(); + const extraVerticalBreathingRoom = 4; + setOptionsPanelTop(height + extraVerticalBreathingRoom); + setOpen((prevIsOpen) => !prevIsOpen); + }} + > + + Search options + + + + +

Include protected contents

+
+ +

Include archived notes

+
+ +

Include trashed notes

+
+
+
+ ); +} + +export const SearchOptionsDirective = toDirective(SearchOptions); diff --git a/app/assets/javascripts/components/Switch.tsx b/app/assets/javascripts/components/Switch.tsx new file mode 100644 index 000000000..44dbf7dc5 --- /dev/null +++ b/app/assets/javascripts/components/Switch.tsx @@ -0,0 +1,48 @@ +import { ComponentChildren, FunctionalComponent } from 'preact'; +import { useState } from 'preact/hooks'; +import { HTMLProps } from 'react'; +import { + CustomCheckboxContainer, + CustomCheckboxInput, + CustomCheckboxInputProps, +} from '@reach/checkbox'; +import '@reach/checkbox/styles.css'; + +export type SwitchProps = HTMLProps & { + checked?: boolean; + onChange: (checked: boolean) => void; + children: ComponentChildren; +}; + +export const Switch: FunctionalComponent = ( + props: SwitchProps +) => { + const [checkedState, setChecked] = useState(props.checked || false); + const checked = props.checked ?? checkedState; + return ( + + ); +}; diff --git a/app/assets/javascripts/components/utils.ts b/app/assets/javascripts/components/utils.ts index f1918328f..eb0f6465b 100644 --- a/app/assets/javascripts/components/utils.ts +++ b/app/assets/javascripts/components/utils.ts @@ -32,6 +32,9 @@ export function toDirective( '$scope', // eslint-disable-next-line @typescript-eslint/no-explicit-any ($element: JQLite, $scope: any) => { + if ($scope.class) { + $element.addClass($scope.class); + } return { $onChanges() { render( diff --git a/app/assets/javascripts/index.ts b/app/assets/javascripts/index.ts index 592300a31..78abf2bf4 100644 --- a/app/assets/javascripts/index.ts +++ b/app/assets/javascripts/index.ts @@ -2,7 +2,6 @@ // css import '@reach/dialog/styles.css'; -import 'sn-stylekit/dist/stylekit.css'; import '../stylesheets/index.css.scss'; // Vendor diff --git a/app/assets/javascripts/services/archiveManager.ts b/app/assets/javascripts/services/archiveManager.ts index 7e3614171..ab64038b1 100644 --- a/app/assets/javascripts/services/archiveManager.ts +++ b/app/assets/javascripts/services/archiveManager.ts @@ -7,17 +7,20 @@ import { PayloadContent, } from '@standardnotes/snjs'; -function zippableTxtName(name: string, suffix = ""): string { - const sanitizedName = name.trim().replace(/[.\\/:"?*|<>]/g, '_'); - const nameEnd = suffix + ".txt"; +function sanitizeFileName(name: string): string { + return name.trim().replace(/[.\\/:"?*|<>]/g, '_'); +} + +function zippableTxtName(name: string, suffix = ''): string { + const sanitizedName = sanitizeFileName(name); + const nameEnd = suffix + '.txt'; const maxFileNameLength = 100; return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd; } export class ArchiveManager { - - private readonly application: WebApplication - private textFile?: string + private readonly application: WebApplication; + private textFile?: string; constructor(application: WebApplication) { this.application = application; @@ -32,10 +35,9 @@ export class ArchiveManager { if (!data) { return; } - const blobData = new Blob( - [JSON.stringify(data, null, 2)], - { type: 'text/json' } - ); + const blobData = new Blob([JSON.stringify(data, null, 2)], { + type: 'text/json', + }); if (encrypted) { this.downloadData( blobData, @@ -81,19 +83,16 @@ export class ArchiveManager { }); } - private async downloadZippedDecryptedItems( - data: BackupFile - ) { + private async downloadZippedDecryptedItems(data: BackupFile) { await this.loadZip(); const items = data.items; this.zip.createWriter( new this.zip.BlobWriter('application/zip'), async (zipWriter: any) => { await new Promise((resolve) => { - const blob = new Blob( - [JSON.stringify(data, null, 2)], - { type: 'text/plain' } - ); + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'text/plain', + }); const fileName = zippableTxtName( 'Standard Notes Backup and Import File.txt' ); @@ -116,7 +115,8 @@ export class ArchiveManager { name = ''; } const blob = new Blob([contents], { type: 'text/plain' }); - const fileName = `Items/${item.content_type}/` + + const fileName = + `Items/${sanitizeFileName(item.content_type)}/` + zippableTxtName(name, `-${item.uuid.split('-')[0]}`); zipWriter.add(fileName, new this.zip.BlobReader(blob), () => { index++; @@ -134,7 +134,9 @@ export class ArchiveManager { }); }; nextFile(); - }, onerror); + }, + onerror + ); } private hrefForData(data: Blob) { diff --git a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts new file mode 100644 index 000000000..bf7489495 --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts @@ -0,0 +1,21 @@ +import { action, makeObservable, observable } from "mobx"; + +export class AccountMenuState { + show = false; + + constructor() { + makeObservable(this, { + show: observable, + setShow: action, + toggleShow: action, + }); + } + + setShow = (show: boolean): void => { + this.show = show; + } + + toggleShow = (): void => { + this.show = !this.show; + } +} diff --git a/app/assets/javascripts/ui_models/app_state/actions_menu_state.ts b/app/assets/javascripts/ui_models/app_state/actions_menu_state.ts new file mode 100644 index 000000000..8ba5dd61e --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/actions_menu_state.ts @@ -0,0 +1,22 @@ +import { UuidString } from "@standardnotes/snjs"; +import { action, makeObservable, observable } from "mobx"; + +export class ActionsMenuState { + hiddenExtensions: Record = {}; + + constructor() { + makeObservable(this, { + hiddenExtensions: observable, + toggleExtensionVisibility: action, + reset: action, + }); + } + + toggleExtensionVisibility = (uuid: UuidString): void => { + this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid]; + } + + reset = (): void => { + this.hiddenExtensions = {}; + } +} diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state/app_state.ts similarity index 77% rename from app/assets/javascripts/ui_models/app_state.ts rename to app/assets/javascripts/ui_models/app_state/app_state.ts index 7b48a0ffb..55024497f 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -7,16 +7,18 @@ import { ContentType, PayloadSource, DeinitSource, - UuidString, - SyncOpStatus, PrefKey, - SNApplication, } from '@standardnotes/snjs'; import { WebApplication } from '@/ui_models/application'; import { Editor } from '@/ui_models/editor'; -import { action, makeObservable, observable, runInAction } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { Bridge } from '@/services/bridge'; import { storage, StorageKey } from '@/services/localStorage'; +import { AccountMenuState } from './account_menu_state'; +import { ActionsMenuState } from './actions_menu_state'; +import { NoAccountWarningState } from './no_account_warning_state'; +import { SyncState } from './sync_state'; +import { SearchOptionsState } from './search_options_state'; export enum AppStateEvent { TagChanged, @@ -41,113 +43,6 @@ export enum EventSource { type ObserverCallback = (event: AppStateEvent, data?: any) => Promise; -class ActionsMenuState { - hiddenExtensions: Record = {}; - - constructor() { - makeObservable(this, { - hiddenExtensions: observable, - toggleExtensionVisibility: action, - reset: action, - }); - } - - toggleExtensionVisibility(uuid: UuidString) { - this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid]; - } - - reset() { - this.hiddenExtensions = {}; - } -} - -export class SyncState { - inProgress = false; - errorMessage?: string = undefined; - humanReadablePercentage?: string = undefined; - - constructor() { - makeObservable(this, { - inProgress: observable, - errorMessage: observable, - humanReadablePercentage: observable, - update: action, - }); - } - - update(status: SyncOpStatus): void { - this.errorMessage = status.error?.message; - this.inProgress = status.syncInProgress; - const stats = status.getStats(); - const completionPercentage = - stats.uploadCompletionCount === 0 - ? 0 - : stats.uploadCompletionCount / stats.uploadTotalCount; - - if (completionPercentage === 0) { - this.humanReadablePercentage = undefined; - } else { - this.humanReadablePercentage = completionPercentage.toLocaleString( - undefined, - { style: 'percent' } - ); - } - } -} - -class AccountMenuState { - show = false; - constructor() { - makeObservable(this, { - show: observable, - setShow: action, - toggleShow: action, - }); - } - setShow(show: boolean) { - this.show = show; - } - toggleShow() { - this.show = !this.show; - } -} - -class NoAccountWarningState { - show: boolean; - constructor(application: SNApplication, appObservers: (() => void)[]) { - this.show = application.hasAccount() - ? false - : storage.get(StorageKey.ShowNoAccountWarning) ?? true; - - appObservers.push( - application.addEventObserver(async () => { - runInAction(() => { - this.show = false; - }); - }, ApplicationEvent.SignedIn), - application.addEventObserver(async () => { - if (application.hasAccount()) { - runInAction(() => { - this.show = false; - }); - } - }, ApplicationEvent.Started) - ); - - makeObservable(this, { - show: observable, - hide: action, - }); - } - hide() { - this.show = false; - storage.set(StorageKey.ShowNoAccountWarning, false); - } - reset() { - storage.remove(StorageKey.ShowNoAccountWarning); - } -} - export class AppState { readonly enableUnfinishedFeatures = isDev || location.host.includes('app-dev.standardnotes.org'); @@ -167,8 +62,8 @@ export class AppState { readonly actionsMenu = new ActionsMenuState(); readonly noAccountWarning: NoAccountWarningState; readonly sync = new SyncState(); + readonly searchOptions; isSessionsModalVisible = false; - mouseUp = Promise.resolve(); private appEventObserverRemovers: (() => void)[] = []; @@ -186,6 +81,10 @@ export class AppState { application, this.appEventObserverRemovers ); + this.searchOptions = new SearchOptionsState( + application, + this.appEventObserverRemovers + ); this.addAppEventObserver(); this.streamNotesAndTags(); this.onVisibilityChange = () => { @@ -196,7 +95,6 @@ export class AppState { this.notifyEvent(event); }; this.registerVisibilityObservers(); - document.addEventListener('mousedown', this.onMouseDown); if (this.bridge.appVersion.includes('-beta')) { this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true; @@ -233,16 +131,9 @@ export class AppState { this.rootScopeCleanup2 = undefined; } document.removeEventListener('visibilitychange', this.onVisibilityChange); - document.removeEventListener('mousedown', this.onMouseDown); this.onVisibilityChange = undefined; } - onMouseDown = (): void => { - this.mouseUp = new Promise((resolve) => { - document.addEventListener('mouseup', () => resolve(), { once: true }); - }); - }; - openSessionsModal() { this.isSessionsModalVisible = true; } diff --git a/app/assets/javascripts/ui_models/app_state/index.ts b/app/assets/javascripts/ui_models/app_state/index.ts new file mode 100644 index 000000000..e5ecceda8 --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/index.ts @@ -0,0 +1,6 @@ +export { + AppState, + AppStateEvent, + EventSource, + PanelResizedData, +} from './app_state'; diff --git a/app/assets/javascripts/ui_models/app_state/no_account_warning_state.ts b/app/assets/javascripts/ui_models/app_state/no_account_warning_state.ts new file mode 100644 index 000000000..63b17f7ab --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/no_account_warning_state.ts @@ -0,0 +1,41 @@ +import { storage, StorageKey } from "@/services/localStorage"; +import { SNApplication, ApplicationEvent } from "@standardnotes/snjs"; +import { runInAction, makeObservable, observable, action } from "mobx"; + +export class NoAccountWarningState { + show: boolean; + constructor(application: SNApplication, appObservers: (() => void)[]) { + this.show = application.hasAccount() + ? false + : storage.get(StorageKey.ShowNoAccountWarning) ?? true; + + appObservers.push( + application.addEventObserver(async () => { + runInAction(() => { + this.show = false; + }); + }, ApplicationEvent.SignedIn), + application.addEventObserver(async () => { + if (application.hasAccount()) { + runInAction(() => { + this.show = false; + }); + } + }, ApplicationEvent.Started) + ); + + makeObservable(this, { + show: observable, + hide: action, + }); + } + + hide = (): void => { + this.show = false; + storage.set(StorageKey.ShowNoAccountWarning, false); + } + + reset = (): void => { + storage.remove(StorageKey.ShowNoAccountWarning); + } +} diff --git a/app/assets/javascripts/ui_models/app_state/search_options_state.ts b/app/assets/javascripts/ui_models/app_state/search_options_state.ts new file mode 100644 index 000000000..7be4be6d2 --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/search_options_state.ts @@ -0,0 +1,59 @@ +import { ApplicationEvent } from "@standardnotes/snjs"; +import { makeObservable, observable, action, runInAction } from "mobx"; +import { WebApplication } from "../application"; + +export class SearchOptionsState { + includeProtectedContents = false; + includeArchived = false; + includeTrashed = false; + + constructor( + private application: WebApplication, + appObservers: (() => void)[] + ) { + makeObservable(this, { + includeProtectedContents: observable, + includeTrashed: observable, + includeArchived: observable, + + toggleIncludeArchived: action, + toggleIncludeTrashed: action, + toggleIncludeProtectedContents: action, + refreshIncludeProtectedContents: action, + }); + + appObservers.push( + this.application.addEventObserver(async () => { + this.refreshIncludeProtectedContents(); + }, ApplicationEvent.ProtectionSessionExpiryDateChanged) + ); + } + + toggleIncludeArchived = (): void => { + this.includeArchived = !this.includeArchived; + }; + + toggleIncludeTrashed = (): void => { + this.includeTrashed = !this.includeTrashed; + }; + + refreshIncludeProtectedContents = (): void => { + if ( + this.includeProtectedContents && + this.application.areProtectionsEnabled() + ) { + this.includeProtectedContents = false; + } + }; + + toggleIncludeProtectedContents = async (): Promise => { + if (this.includeProtectedContents) { + this.includeProtectedContents = false; + } else { + const authorized = await this.application.authorizeSearchingProtectedNotesText(); + runInAction(() => { + this.includeProtectedContents = authorized; + }); + } + }; +} diff --git a/app/assets/javascripts/ui_models/app_state/sync_state.ts b/app/assets/javascripts/ui_models/app_state/sync_state.ts new file mode 100644 index 000000000..7bc066454 --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/sync_state.ts @@ -0,0 +1,36 @@ +import { SyncOpStatus } from "@standardnotes/snjs"; +import { action, makeObservable, observable } from "mobx"; + +export class SyncState { + inProgress = false; + errorMessage?: string = undefined; + humanReadablePercentage?: string = undefined; + + constructor() { + makeObservable(this, { + inProgress: observable, + errorMessage: observable, + humanReadablePercentage: observable, + update: action, + }); + } + + update = (status: SyncOpStatus): void => { + this.errorMessage = status.error?.message; + this.inProgress = status.syncInProgress; + const stats = status.getStats(); + const completionPercentage = + stats.uploadCompletionCount === 0 + ? 0 + : stats.uploadCompletionCount / stats.uploadTotalCount; + + if (completionPercentage === 0) { + this.humanReadablePercentage = undefined; + } else { + this.humanReadablePercentage = completionPercentage.toLocaleString( + undefined, + { style: 'percent' } + ); + } + } +} diff --git a/app/assets/javascripts/views/notes/notes-view.pug b/app/assets/javascripts/views/notes/notes-view.pug index 2439b9c8e..26b4a8eb3 100644 --- a/app/assets/javascripts/views/notes/notes-view.pug +++ b/app/assets/javascripts/views/notes/notes-view.pug @@ -12,6 +12,7 @@ i.icon.ion-plus.add-button .filter-section(role='search') input#search-bar.filter-bar( + type="text" ng-ref='self.searchBarInput' ng-focus='self.onSearchInputFocus()' ng-blur='self.onSearchInputBlur()', @@ -25,21 +26,12 @@ #search-clear-button( ng-click='self.clearFilterText();', ng-show='self.state.noteFilter.text' + aria-role="button" ) ✕ - label.sk-panel-row.justify-left.mt-2.animate-slide-in-top( - ng-if='self.state.searchIsFocused || self.state.searchOptionsAreFocused || self.state.authorizingSearchOptions' - style="padding-bottom: 0" - ) - .sk-horizontal-group.tight - input( - ng-ref='self.searchOptionsInput' - ng-focus="self.onSearchOptionsFocus()" - ng-blur="self.onSearchOptionsBlur()" - type="checkbox" - ng-checked="self.state.noteFilter.includeProtectedNoteText" - ng-on-click="self.onIncludeProtectedNoteTextChange($event)" - ) - p.sk-p.capitalize Include protected contents + search-options( + class="ml-2 h-20px" + app-state='self.appState' + ) no-account-warning( application='self.application' app-state='self.appState' diff --git a/app/assets/javascripts/views/notes/notes_view.ts b/app/assets/javascripts/views/notes/notes_view.ts index 5eee017f7..7d04358a6 100644 --- a/app/assets/javascripts/views/notes/notes_view.ts +++ b/app/assets/javascripts/views/notes/notes_view.ts @@ -18,10 +18,11 @@ import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { PANEL_NAME_NOTES } from '@/views/constants'; +import { autorun, IReactionDisposer } from 'mobx'; type NotesState = { panelTitle: string - notes?: SNNote[] + notes: SNNote[] renderedNotes: SNNote[] renderedNotesTags: string[], sortBy?: string @@ -33,11 +34,12 @@ type NotesState = { hideTags: boolean noteFilter: { text: string; - includeProtectedNoteText: boolean; } - searchIsFocused: boolean; - searchOptionsAreFocused: boolean; - authorizingSearchOptions: boolean; + searchOptions: { + includeProtectedContents: boolean; + includeArchived: boolean; + includeTrashed: boolean; + } mutable: { showMenu: boolean } completedFullSync: boolean [PrefKey.TagsPanelWidth]?: number @@ -76,8 +78,7 @@ class NotesViewCtrl extends PureViewCtrl { private searchKeyObserver: any private noteFlags: Partial> = {} private removeObservers: Array<() => void> = []; - private searchBarInput?: JQLite; - private searchOptionsInput?: JQLite; + private appStateObserver?: IReactionDisposer; /* @ngInject */ constructor($timeout: ng.ITimeoutService,) { @@ -94,6 +95,24 @@ class NotesViewCtrl extends PureViewCtrl { this.onPanelResize = this.onPanelResize.bind(this); window.addEventListener('resize', this.onWindowResize, true); this.registerKeyboardShortcuts(); + this.appStateObserver = autorun(async () => { + const { + includeProtectedContents, + includeArchived, + includeTrashed, + } = this.appState.searchOptions; + await this.setState({ + searchOptions: { + includeProtectedContents, + includeArchived, + includeTrashed, + } + }); + if (this.state.noteFilter.text) { + this.reloadNotesDisplayOptions(); + this.reloadNotes(); + } + }); } onWindowResize() { @@ -112,6 +131,7 @@ class NotesViewCtrl extends PureViewCtrl { this.nextNoteKeyObserver(); this.previousNoteKeyObserver(); this.searchKeyObserver(); + this.appStateObserver?.(); this.newNoteKeyObserver = undefined; this.nextNoteKeyObserver = undefined; this.previousNoteKeyObserver = undefined; @@ -119,10 +139,6 @@ class NotesViewCtrl extends PureViewCtrl { super.deinit(); } - getState() { - return this.state as NotesState; - } - async setNotesState(state: Partial) { return this.setState(state); } @@ -135,14 +151,15 @@ class NotesViewCtrl extends PureViewCtrl { mutable: { showMenu: false }, noteFilter: { text: '', - includeProtectedNoteText: false + }, + searchOptions: { + includeArchived: false, + includeProtectedContents: false, + includeTrashed: false, }, panelTitle: '', completedFullSync: false, - hideTags: true, - searchIsFocused: false, - searchOptionsAreFocused: false, - authorizingSearchOptions: false + hideTags: true }; } @@ -203,7 +220,7 @@ class NotesViewCtrl extends PureViewCtrl { * that may be in progress. This is the sync alternative to `async getMostValidNotes` */ private getPossiblyStaleNotes() { - return this.getState().notes!; + return this.state.notes; } /** @@ -230,7 +247,7 @@ class NotesViewCtrl extends PureViewCtrl { } streamNotesAndTags() { - this.removeObservers.push(this.application!.streamItems( + this.removeObservers.push(this.application.streamItems( [ContentType.Note], async (items) => { const notes = items as SNNote[]; @@ -256,7 +273,7 @@ class NotesViewCtrl extends PureViewCtrl { } )); - this.removeObservers.push(this.application!.streamItems( + this.removeObservers.push(this.application.streamItems( [ContentType.Tag], async (items) => { const tags = items as SNTag[]; @@ -280,9 +297,9 @@ class NotesViewCtrl extends PureViewCtrl { } async createNewNote() { - let title = `Note ${this.getState().notes!.length + 1}`; + let title = `Note ${this.state.notes.length + 1}`; if (this.isFiltering()) { - title = this.getState().noteFilter.text; + title = this.state.noteFilter.text; } await this.appState.createEditor(title); await this.flushUI(); @@ -293,21 +310,21 @@ class NotesViewCtrl extends PureViewCtrl { this.resetScrollPosition(); this.setShowMenuFalse(); await this.setNoteFilterText(''); - this.application!.getDesktopService().searchText(); + this.application.getDesktopService().searchText(); this.resetPagination(); /* Capture db load state before beginning reloadNotes, since this status may change during reload */ - const dbLoaded = this.application!.isDatabaseLoaded(); + const dbLoaded = this.application.isDatabaseLoaded(); this.reloadNotesDisplayOptions(); await this.reloadNotes(); - if (this.getState().notes!.length > 0) { + if (this.state.notes.length > 0) { this.selectFirstNote(); } else if (dbLoaded) { if ( this.activeEditorNote && - !this.getState().notes!.includes(this.activeEditorNote!) + !this.state.notes.includes(this.activeEditorNote!) ) { this.appState.closeActiveEditor(); } @@ -323,7 +340,7 @@ class NotesViewCtrl extends PureViewCtrl { } async removeNoteFromList(note: SNNote) { - const notes = this.getState().notes!; + const notes = this.state.notes; removeFromArray(notes, note); await this.setNotesState({ notes: notes, @@ -343,23 +360,37 @@ class NotesViewCtrl extends PureViewCtrl { */ private reloadNotesDisplayOptions() { const tag = this.appState.selectedTag; - const searchText = this.getState().noteFilter.text.toLowerCase(); + + const searchText = this.state.noteFilter.text.toLowerCase(); + const isSearching = searchText.length; + let includeArchived: boolean; + let includeTrashed: boolean; + + if (isSearching) { + includeArchived = this.state.searchOptions.includeArchived; + includeTrashed = this.state.searchOptions.includeTrashed; + } else { + includeArchived = this.state.showArchived ?? false; + includeTrashed = false; + } + const criteria = NotesDisplayCriteria.Create({ - sortProperty: this.state.sortBy! as CollectionSort, - sortDirection: this.state.sortReverse! ? 'asc' : 'dsc', + sortProperty: this.state.sortBy as CollectionSort, + sortDirection: this.state.sortReverse ? 'asc' : 'dsc', tags: tag ? [tag] : [], - includeArchived: this.getState().showArchived!, - includePinned: !this.getState().hidePinned!, + includeArchived, + includeTrashed, + includePinned: !this.state.hidePinned, searchQuery: { - query: searchText ?? '', - includeProtectedNoteText: this.state.noteFilter.includeProtectedNoteText + query: searchText, + includeProtectedNoteText: this.state.searchOptions.includeProtectedContents } }); - this.application!.setNotesDisplayCriteria(criteria); + this.application.setNotesDisplayCriteria(criteria); } private get selectedTag() { - return this.application!.getAppState().getSelectedTag(); + return this.application.getAppState().getSelectedTag(); } private async performReloadNotes() { @@ -411,7 +442,7 @@ class NotesViewCtrl extends PureViewCtrl { setShowMenuFalse() { this.setNotesState({ mutable: { - ...this.getState().mutable, + ...this.state.mutable, showMenu: false } }); @@ -420,19 +451,19 @@ class NotesViewCtrl extends PureViewCtrl { async handleEditorChange() { const activeNote = this.appState.getActiveEditor()?.note; if (activeNote && activeNote.conflictOf) { - this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => { + this.application.changeAndSaveItem(activeNote.uuid, (mutator) => { mutator.conflictOf = undefined; }); } if (this.isFiltering()) { - this.application!.getDesktopService().searchText(this.getState().noteFilter.text); + this.application.getDesktopService().searchText(this.state.noteFilter.text); } } async reloadPreferences() { const viewOptions = {} as NotesState; - const prevSortValue = this.getState().sortBy; - let sortBy = this.application!.getPreference( + const prevSortValue = this.state.sortBy; + let sortBy = this.application.getPreference( PrefKey.SortNotesBy, CollectionSort.CreatedAt ); @@ -444,23 +475,23 @@ class NotesViewCtrl extends PureViewCtrl { sortBy = CollectionSort.UpdatedAt; } viewOptions.sortBy = sortBy; - viewOptions.sortReverse = this.application!.getPreference( + viewOptions.sortReverse = this.application.getPreference( PrefKey.SortNotesReverse, false ); - viewOptions.showArchived = this.application!.getPreference( + viewOptions.showArchived = this.application.getPreference( PrefKey.NotesShowArchived, false ); - viewOptions.hidePinned = this.application!.getPreference( + viewOptions.hidePinned = this.application.getPreference( PrefKey.NotesHidePinned, false ); - viewOptions.hideNotePreview = this.application!.getPreference( + viewOptions.hideNotePreview = this.application.getPreference( PrefKey.NotesHideNotePreview, false ); - viewOptions.hideDate = this.application!.getPreference( + viewOptions.hideDate = this.application.getPreference( PrefKey.NotesHideDate, false ); @@ -468,7 +499,7 @@ class NotesViewCtrl extends PureViewCtrl { PrefKey.NotesHideTags, true, ); - const state = this.getState(); + const state = this.state; const displayOptionsChanged = ( viewOptions.sortBy !== state.sortBy || viewOptions.sortReverse !== state.sortReverse || @@ -490,13 +521,13 @@ class NotesViewCtrl extends PureViewCtrl { } reloadPanelWidth() { - const width = this.application!.getPreference( + const width = this.application.getPreference( PrefKey.NotesPanelWidth ); if (width && this.panelPuppet!.ready) { this.panelPuppet!.setWidth!(width); if (this.panelPuppet!.isCollapsed!()) { - this.application!.getAppState().panelDidResize( + this.application.getAppState().panelDidResize( PANEL_NAME_NOTES, this.panelPuppet!.isCollapsed!() ); @@ -510,11 +541,11 @@ class NotesViewCtrl extends PureViewCtrl { __: boolean, isCollapsed: boolean ) { - this.application!.setPreference( + this.application.setPreference( PrefKey.NotesPanelWidth, newWidth ); - this.application!.getAppState().panelDidResize( + this.application.getAppState().panelDidResize( PANEL_NAME_NOTES, isCollapsed ); @@ -524,7 +555,7 @@ class NotesViewCtrl extends PureViewCtrl { this.notesToDisplay += this.pageSize; this.reloadNotes(); if (this.searchSubmitted) { - this.application!.getDesktopService().searchText(this.getState().noteFilter.text); + this.application.getDesktopService().searchText(this.state.noteFilter.text); } } @@ -543,7 +574,7 @@ class NotesViewCtrl extends PureViewCtrl { reloadPanelTitle() { let title; if (this.isFiltering()) { - const resultCount = this.getState().notes!.length; + const resultCount = this.state.notes.length; title = `${resultCount} search results`; } else if (this.appState.selectedTag) { title = `${this.appState.selectedTag.title}`; @@ -555,20 +586,20 @@ class NotesViewCtrl extends PureViewCtrl { optionsSubtitle() { let base = ""; - if (this.getState().sortBy === CollectionSort.CreatedAt) { + if (this.state.sortBy === CollectionSort.CreatedAt) { base += " Date Added"; - } else if (this.getState().sortBy === CollectionSort.UpdatedAt) { + } else if (this.state.sortBy === CollectionSort.UpdatedAt) { base += " Date Modified"; - } else if (this.getState().sortBy === CollectionSort.Title) { + } else if (this.state.sortBy === CollectionSort.Title) { base += " Title"; } - if (this.getState().showArchived) { + if (this.state.showArchived) { base += " | + Archived"; } - if (this.getState().hidePinned) { + if (this.state.hidePinned) { base += " | – Pinned"; } - if (this.getState().sortReverse) { + if (this.state.sortReverse) { base += " | Reversed"; } return base; @@ -628,13 +659,8 @@ class NotesViewCtrl extends PureViewCtrl { this.noteFlags[note.uuid] = flags; } - displayableNotes() { - return this.getState().notes!; - } - getFirstNonProtectedNote() { - const displayableNotes = this.displayableNotes(); - return displayableNotes.find(note => !note.protected); + return this.state.notes.find(note => !note.protected); } selectFirstNote() { @@ -645,9 +671,9 @@ class NotesViewCtrl extends PureViewCtrl { } selectNextNote() { - const displayableNotes = this.displayableNotes(); + const displayableNotes = this.state.notes; const currentIndex = displayableNotes.findIndex((candidate) => { - return candidate.uuid === this.activeEditorNote!.uuid; + return candidate.uuid === this.activeEditorNote.uuid; }); if (currentIndex + 1 < displayableNotes.length) { this.selectNote(displayableNotes[currentIndex + 1]); @@ -664,7 +690,7 @@ class NotesViewCtrl extends PureViewCtrl { } selectPreviousNote() { - const displayableNotes = this.displayableNotes(); + const displayableNotes = this.state.notes; const currentIndex = displayableNotes.indexOf(this.activeEditorNote!); if (currentIndex - 1 >= 0) { this.selectNote(displayableNotes[currentIndex - 1]); @@ -675,14 +701,14 @@ class NotesViewCtrl extends PureViewCtrl { } isFiltering() { - return this.getState().noteFilter.text && - this.getState().noteFilter.text.length > 0; + return this.state.noteFilter.text && + this.state.noteFilter.text.length > 0; } async setNoteFilterText(text: string) { await this.setNotesState({ noteFilter: { - ...this.getState().noteFilter, + ...this.state.noteFilter, text: text } }); @@ -703,72 +729,8 @@ class NotesViewCtrl extends PureViewCtrl { await this.reloadNotes(); } - async onIncludeProtectedNoteTextChange(event: Event) { - this.searchBarInput?.[0].focus(); - if (this.state.noteFilter.includeProtectedNoteText) { - await this.setState({ - noteFilter: { - ...this.state.noteFilter, - includeProtectedNoteText: false, - }, - }); - this.reloadNotesDisplayOptions(); - await this.reloadNotes(); - } else { - this.setState({ - authorizingSearchOptions: true, - }); - event.preventDefault(); - if (await this.application.authorizeSearchingProtectedNotesText()) { - await this.setState({ - noteFilter: { - ...this.state.noteFilter, - includeProtectedNoteText: true, - }, - }); - this.reloadNotesDisplayOptions(); - await this.reloadNotes(); - } - await this.$timeout(50); - await this.setState({ - authorizingSearchOptions: false, - }); - } - } - - onSearchInputFocus() { - this.setState({ - searchIsFocused: true, - }); - } - async onSearchInputBlur() { - await this.appState.mouseUp; - /** - * Wait a non-zero amount of time so the newly-focused element can have - * enough time to set its state - */ - await this.$timeout(50); - await this.setState({ - searchIsFocused: - this.searchBarInput?.[0] === document.activeElement, - }); - this.onFilterEnter(); - } - - onSearchOptionsFocus() { - this.setState({ - searchOptionsAreFocused: true, - }); - } - - async onSearchOptionsBlur() { - await this.appState.mouseUp; - await this.$timeout(50); - this.setState({ - searchOptionsAreFocused: - this.searchOptionsInput?.[0] === document.activeElement, - }); + this.appState.searchOptions.refreshIncludeProtectedContents(); } onFilterEnter() { @@ -778,7 +740,7 @@ class NotesViewCtrl extends PureViewCtrl { * enter before highlighting desktop search results. */ this.searchSubmitted = true; - this.application!.getDesktopService().searchText(this.getState().noteFilter.text); + this.application.getDesktopService().searchText(this.state.noteFilter.text); } selectedMenuItem() { @@ -786,7 +748,7 @@ class NotesViewCtrl extends PureViewCtrl { } togglePrefKey(key: PrefKey) { - this.application!.setPreference( + this.application.setPreference( key, !this.state[key] ); @@ -806,14 +768,14 @@ class NotesViewCtrl extends PureViewCtrl { toggleReverseSort() { this.selectedMenuItem(); - this.application!.setPreference( + this.application.setPreference( PrefKey.SortNotesReverse, - !this.getState().sortReverse + !this.state.sortReverse ); } setSortBy(type: CollectionSort) { - this.application!.setPreference( + this.application.setPreference( PrefKey.SortNotesBy, type ); @@ -829,7 +791,7 @@ class NotesViewCtrl extends PureViewCtrl { * use Control modifier as well. These rules don't apply to desktop, but * probably better to be consistent. */ - this.newNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ + this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: 'n', modifiers: [ KeyboardModifier.Meta, @@ -841,7 +803,7 @@ class NotesViewCtrl extends PureViewCtrl { } }); - this.nextNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ + this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: KeyboardKey.Down, elements: [ document.body, @@ -856,7 +818,7 @@ class NotesViewCtrl extends PureViewCtrl { } }); - this.previousNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ + this.previousNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: KeyboardKey.Up, element: document.body, onKeyDown: () => { @@ -864,7 +826,7 @@ class NotesViewCtrl extends PureViewCtrl { } }); - this.searchKeyObserver = this.application!.getKeyboardService().addKeyObserver({ + this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: "f", modifiers: [ KeyboardModifier.Meta, diff --git a/app/assets/stylesheets/_notes.scss b/app/assets/stylesheets/_notes.scss index 02a818464..e66e8d54c 100644 --- a/app/assets/stylesheets/_notes.scss +++ b/app/assets/stylesheets/_notes.scss @@ -53,6 +53,8 @@ height: 28px; margin-top: 14px; position: relative; + display: flex; + align-items: center; .filter-bar { background-color: var(--sn-stylekit-contrast-background-color); @@ -82,7 +84,8 @@ position: absolute; top: 50%; transform: translateY(-50%); - right: 8px; + right: 32px; + cursor: pointer; transition: background-color 0.15s linear; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss new file mode 100644 index 000000000..b3103034e --- /dev/null +++ b/app/assets/stylesheets/_sn.scss @@ -0,0 +1,59 @@ +/* Components and utilities that have yet to be extracted to StyleKit. */ + +.sn-dropdown { + @extend .absolute; + @extend .bg-default; + @extend .min-w-80; + @extend .duration-150; + @extend .grid; + @extend .gap-2; + @extend .slide-down-animation; + @extend .rounded; + @extend .box-shadow; + + z-index: $z-index-dropdown-menu; + + &.sn-dropdown-anchor-right { + right: 0; + } + + &[data-state="collapsed"] { + display: none; + } +} + +/** Lesser specificity will give priority to reach's styles */ +[data-reach-custom-checkbox-container].sn-switch { + @extend .duration-150; + @extend .ease-out; + @extend .rounded-full; + @extend .transition-background; + width: 31px; + height: 18px; + + @extend .cursor-pointer; + @extend .focus-within\:padded-ring-info; + @extend .focus-within\:outline-none; +} + +.sn-switch-handle { + @extend .absolute; + @extend .block; + left: 2px; + width: 14px; + height: 14px; + top: 50%; + + @extend .bg-default; + @extend .rounded-full; + + @extend .ease-out; + @extend .transition-transform; + @extend .duration-150; + + transform: translate(0px, -50%); + + &.sn-switch-handle-right { + transform: translate(31px - 18px, -50%); + } +} diff --git a/app/assets/stylesheets/_ui.scss b/app/assets/stylesheets/_ui.scss index e4785998d..e71c8810d 100644 --- a/app/assets/stylesheets/_ui.scss +++ b/app/assets/stylesheets/_ui.scss @@ -183,9 +183,6 @@ $screen-md-max: ($screen-lg-min - 1) !default; grid-column-end: 3; } -.color-neutral { - color: var(--sn-stylekit-neutral-color) -} .hover\:color-info:hover { color: var(--sn-stylekit-info-color) } diff --git a/app/assets/stylesheets/index.css.scss b/app/assets/stylesheets/index.css.scss index 52ca8b997..fa0deecc9 100644 --- a/app/assets/stylesheets/index.css.scss +++ b/app/assets/stylesheets/index.css.scss @@ -1,3 +1,4 @@ +@import "sn-stylekit/dist/stylekit"; @import "main"; @import "ui"; @import "footer"; @@ -11,3 +12,4 @@ @import "ionicons"; @import "reach-sub"; @import "sessions-modal"; +@import "sn"; diff --git a/package.json b/package.json index bdfa4a1a1..f90b34491 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "@babel/plugin-transform-react-jsx": "^7.12.16", "@babel/preset-env": "^7.12.16", "@babel/preset-typescript": "^7.12.16", + "@reach/disclosure": "^0.14.0", + "@reach/visually-hidden": "^0.14.0", "@svgr/webpack": "^5.5.0", "@types/angular": "^1.8.0", "@types/lodash": "^4.14.168", @@ -53,19 +55,20 @@ "pug-loader": "^2.4.0", "sass-loader": "^8.0.2", "serve-static": "^1.14.1", - "sn-stylekit": "github:standardnotes/StyleKit#1ec13454cc6d8cf97651263a039de8872be527b8", + "sn-stylekit": "github:standardnotes/StyleKit#c88aafa634c1e40d2b8f6e474f44a67805f9c172", "ts-loader": "^8.0.17", "typescript": "^4.1.5", "typescript-eslint": "0.0.1-alpha.0", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.2", - "webpack-merge": "^4.2.2" + "webpack-merge": "^5.7.3" }, "dependencies": { "@bugsnag/js": "^7.6.0", "@reach/alert": "^0.13.0", "@reach/alert-dialog": "^0.13.0", + "@reach/checkbox": "^0.13.2", "@reach/dialog": "^0.13.0", "@standardnotes/sncrypto-web": "^1.2.10", "@standardnotes/snjs": "^2.0.70", diff --git a/webpack.dev.js b/webpack.dev.js index b5605f807..f793c8a00 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -1,4 +1,4 @@ -const merge = require('webpack-merge'); +const { merge } = require('webpack-merge'); const config = require('./webpack.config.js'); const HtmlWebpackPlugin = require('html-webpack-plugin'); diff --git a/webpack.prod.js b/webpack.prod.js index bd0750354..855097710 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -1,7 +1,9 @@ -const merge = require('webpack-merge'); +const { merge } = require('webpack-merge'); const config = require('./webpack.config.js'); -module.exports = (env, argv) => merge(config(env, argv), { - mode: 'production', - devtool: 'source-map', -}); +module.exports = (env, argv) => { + return merge(config(env, argv), { + mode: 'production', + devtool: 'source-map', + }); +}; diff --git a/yarn.lock b/yarn.lock index 9bace72f1..722211feb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1810,6 +1810,33 @@ "@reach/utils" "0.13.0" tslib "^2.0.0" +"@reach/auto-id@0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.13.2.tgz#6c7fe7142285707b0f38b2f556f32accb467df32" + integrity sha512-dWeXt6xxjN+NPRoZFXgmNkF89t8MEPsWLYjIIDf3gNXA/Dxaoytc9YBOIfVGpDSpdOwxPpxOu8rH+4Y3Jk2gHA== + dependencies: + "@reach/utils" "0.13.2" + tslib "^2.1.0" + +"@reach/auto-id@0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.14.0.tgz#1edfdeddb8fad656ec894a934c78f20c3b341416" + integrity sha512-Ym8PBXubuXP7m8CFReqHIH8iXOTcZ3PuAjtHHGuc2EToCwhbbFSf1OouWMUdr6S6vZmu0pOnhOgIE1QGw+dEpw== + dependencies: + "@reach/utils" "0.14.0" + tslib "^2.1.0" + +"@reach/checkbox@^0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@reach/checkbox/-/checkbox-0.13.2.tgz#b972b922cf6cea0c2bbabca0129c58307b566a4e" + integrity sha512-PffLj9G7y7cnm53/Ni52DrbHUGbB2ohhe3KVQH6CMY3fuRAN00kJEkIoWR2Yh5VON3lxS9E6vEFn4c+3F6dYYA== + dependencies: + "@reach/auto-id" "0.13.2" + "@reach/machine" "0.13.2" + "@reach/utils" "0.13.2" + prop-types "^15.7.2" + tslib "^2.1.0" + "@reach/dialog@0.13.0", "@reach/dialog@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.13.0.tgz#2110725c3b8a3c64685834cdc9f3ce5c15617809" @@ -1822,6 +1849,25 @@ react-remove-scroll "^2.4.1" tslib "^2.0.0" +"@reach/disclosure@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@reach/disclosure/-/disclosure-0.14.0.tgz#0b1a4df7f8c407cb08b6ae71615bb096fb87b6f7" + integrity sha512-J6OJouJiuA71k07LGLqARV6bigxOjxOMCvQbXzvT1ppVH6rWU6nFhX/gweCUxUv5BwrWYQp51omVoSHClI3TRg== + dependencies: + "@reach/auto-id" "0.14.0" + "@reach/utils" "0.14.0" + prop-types "^15.7.2" + tslib "^2.1.0" + +"@reach/machine@0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@reach/machine/-/machine-0.13.2.tgz#744302f5ce2d4e5fd0527ae0baa60d325b2325d8" + integrity sha512-sYwkap6s4Rh2Uvs1IwMH537UJ1lHalMjjQlIV1cgruvOSVLvEB4WiGZpiKU6OS4WPVFW+iYuJwNW1mbGGSJkQg== + dependencies: + "@reach/utils" "0.13.2" + "@xstate/fsm" "1.4.0" + tslib "^2.1.0" + "@reach/portal@0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.13.0.tgz#bed220d41097deb1454a7928b22529ba10d3ea2b" @@ -1839,6 +1885,24 @@ tslib "^2.0.0" warning "^4.0.3" +"@reach/utils@0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.13.2.tgz#87e8fef8ebfe583fa48250238a1a3ed03189fcc8" + integrity sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ== + dependencies: + "@types/warning" "^3.0.0" + tslib "^2.1.0" + warning "^4.0.3" + +"@reach/utils@0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.14.0.tgz#f3ff579c737c3e9528f6f940bc518452f2636810" + integrity sha512-QbSFO5p44qUCkOllJM06Lt5A/EsoVIYlB9Ij1vKEezy53keaa5bc879dD2Ahv+mMf+E1VppCeiL6YYvdpJmVVQ== + dependencies: + "@types/warning" "^3.0.0" + tslib "^2.1.0" + warning "^4.0.3" + "@reach/visually-hidden@0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.13.0.tgz#cace36d9bb80ffb797374fcaea989391b881038f" @@ -1846,6 +1910,14 @@ dependencies: tslib "^2.0.0" +"@reach/visually-hidden@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.14.0.tgz#04df6dff0da1180ab85b8548c9a2113a7f01ebd0" + integrity sha512-vyZNj8p1piBY6FR280dHRQxjZGyFVRXYQY7yxvEIe251EZib6C0jc66YlcA/+om4gEhVcJ17/O/bHnKV7GRIig== + dependencies: + prop-types "^15.7.2" + tslib "^2.1.0" + "@standardnotes/auth@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-2.0.0.tgz#93f633fd40855f87843f911109e92b29dcbc5a04" @@ -2305,6 +2377,11 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@xstate/fsm@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.4.0.tgz#6fd082336fde4d026e9e448576189ee5265fa51a" + integrity sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -7733,9 +7810,9 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -"sn-stylekit@github:standardnotes/StyleKit#1ec13454cc6d8cf97651263a039de8872be527b8": - version "3.0.1" - resolved "https://codeload.github.com/standardnotes/StyleKit/tar.gz/1ec13454cc6d8cf97651263a039de8872be527b8" +"sn-stylekit@github:standardnotes/StyleKit#c88aafa634c1e40d2b8f6e474f44a67805f9c172": + version "4.0.0" + resolved "https://codeload.github.com/standardnotes/StyleKit/tar.gz/c88aafa634c1e40d2b8f6e474f44a67805f9c172" snapdragon-node@^2.0.1: version "2.1.1" @@ -8349,6 +8426,11 @@ tslib@^2.0.0, tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -8738,12 +8820,13 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== dependencies: - lodash "^4.17.15" + clone-deep "^4.0.1" + wildcard "^2.0.0" webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: version "1.4.3" @@ -8822,6 +8905,11 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"