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
This commit is contained in:
Baptiste Grob
2021-04-06 16:48:25 +02:00
committed by GitHub
parent 275c8cbd1f
commit ed69680295
24 changed files with 672 additions and 319 deletions

View 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 14.1667V15.8333H7.5V14.1667H2.5ZM2.5 4.16667V5.83333H10.8333V4.16667H2.5ZM10.8333 17.5V15.8333H17.5V14.1667H10.8333V12.5H9.16667V17.5H10.8333ZM5.83333 7.5V9.16667H2.5V10.8333H5.83333V12.5H7.5V7.5H5.83333ZM17.5 10.8333V9.16667H9.16667V10.8333H17.5ZM12.5 7.5H14.1667V5.83333H17.5V4.16667H14.1667V2.5H12.5V7.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 429 B

View File

@@ -58,6 +58,7 @@ import { Bridge } from './services/bridge';
import { SessionsModalDirective } from './components/SessionsModal'; import { SessionsModalDirective } from './components/SessionsModal';
import { NoAccountWarningDirective } from './components/NoAccountWarning'; import { NoAccountWarningDirective } from './components/NoAccountWarning';
import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning'; import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning';
import { SearchOptionsDirective } from './components/SearchOptions';
function reloadHiddenFirefoxTab(): boolean { function reloadHiddenFirefoxTab(): boolean {
/** /**
@@ -145,7 +146,8 @@ const startApplication: StartApplication = async function startApplication(
.directive('syncResolutionMenu', () => new SyncResolutionMenu()) .directive('syncResolutionMenu', () => new SyncResolutionMenu())
.directive('sessionsModal', SessionsModalDirective) .directive('sessionsModal', SessionsModalDirective)
.directive('noAccountWarning', NoAccountWarningDirective) .directive('noAccountWarning', NoAccountWarningDirective)
.directive('protectedNotePanel', NoProtectionsdNoteWarningDirective); .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective)
.directive('searchOptions', SearchOptionsDirective);
// Filters // Filters
angular.module('app').filter('trusted', ['$sce', trusted]); angular.module('app').filter('trusted', ['$sce', trusted]);

View File

@@ -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<HTMLButtonElement>();
const panelRef = useRef<HTMLDivElement>();
function closeOnBlur(event: FocusEvent<HTMLElement>) {
if (
!togglingIncludeProtectedContents &&
!panelRef.current.contains(event.relatedTarget as Node)
) {
setOpen(false);
}
}
return (
<Disclosure
open={open}
onChange={() => {
const { height } = buttonRef.current.getBoundingClientRect();
const extraVerticalBreathingRoom = 4;
setOptionsPanelTop(height + extraVerticalBreathingRoom);
setOpen((prevIsOpen) => !prevIsOpen);
}}
>
<DisclosureButton
ref={buttonRef}
onBlur={closeOnBlur}
className="sn-icon-button color-neutral hover:color-info"
>
<VisuallyHidden>Search options</VisuallyHidden>
<TuneIcon className="fill-current block" />
</DisclosureButton>
<DisclosurePanel
ref={panelRef}
style={{
top: optionsPanelTop,
}}
className="sn-dropdown sn-dropdown-anchor-right grid gap-2 py-2"
>
<Switch
checked={includeProtectedContents}
onChange={toggleIncludeProtectedContents}
onBlur={closeOnBlur}
>
<p className="capitalize">Include protected contents</p>
</Switch>
<Switch
checked={includeArchived}
onChange={searchOptions.toggleIncludeArchived}
onBlur={closeOnBlur}
>
<p className="capitalize">Include archived notes</p>
</Switch>
<Switch
checked={includeTrashed}
onChange={searchOptions.toggleIncludeTrashed}
onBlur={closeOnBlur}
>
<p className="capitalize">Include trashed notes</p>
</Switch>
</DisclosurePanel>
</Disclosure>
);
}
export const SearchOptionsDirective = toDirective<Props>(SearchOptions);

View File

@@ -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<HTMLInputElement> & {
checked?: boolean;
onChange: (checked: boolean) => void;
children: ComponentChildren;
};
export const Switch: FunctionalComponent<SwitchProps> = (
props: SwitchProps
) => {
const [checkedState, setChecked] = useState(props.checked || false);
const checked = props.checked ?? checkedState;
return (
<label className="sn-component flex justify-between items-center cursor-pointer hover:bg-contrast py-2 px-3">
{props.children}
<CustomCheckboxContainer
checked={checked}
onChange={(event) => {
setChecked(event.target.checked);
props.onChange(event.target.checked);
}}
className={`sn-switch ${checked ? 'bg-info' : 'bg-secondary-contrast'}`}
>
<CustomCheckboxInput
{...({
...props,
children: undefined,
} as CustomCheckboxInputProps)}
/>
<span
aria-hidden
className={`sn-switch-handle ${
checked ? 'sn-switch-handle-right' : ''
}`}
/>
</CustomCheckboxContainer>
</label>
);
};

View File

@@ -32,6 +32,9 @@ export function toDirective<Props>(
'$scope', '$scope',
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
($element: JQLite, $scope: any) => { ($element: JQLite, $scope: any) => {
if ($scope.class) {
$element.addClass($scope.class);
}
return { return {
$onChanges() { $onChanges() {
render( render(

View File

@@ -2,7 +2,6 @@
// css // css
import '@reach/dialog/styles.css'; import '@reach/dialog/styles.css';
import 'sn-stylekit/dist/stylekit.css';
import '../stylesheets/index.css.scss'; import '../stylesheets/index.css.scss';
// Vendor // Vendor

View File

@@ -7,17 +7,20 @@ import {
PayloadContent, PayloadContent,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
function zippableTxtName(name: string, suffix = ""): string { function sanitizeFileName(name: string): string {
const sanitizedName = name.trim().replace(/[.\\/:"?*|<>]/g, '_'); return name.trim().replace(/[.\\/:"?*|<>]/g, '_');
const nameEnd = suffix + ".txt"; }
function zippableTxtName(name: string, suffix = ''): string {
const sanitizedName = sanitizeFileName(name);
const nameEnd = suffix + '.txt';
const maxFileNameLength = 100; const maxFileNameLength = 100;
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd; return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd;
} }
export class ArchiveManager { export class ArchiveManager {
private readonly application: WebApplication;
private readonly application: WebApplication private textFile?: string;
private textFile?: string
constructor(application: WebApplication) { constructor(application: WebApplication) {
this.application = application; this.application = application;
@@ -32,10 +35,9 @@ export class ArchiveManager {
if (!data) { if (!data) {
return; return;
} }
const blobData = new Blob( const blobData = new Blob([JSON.stringify(data, null, 2)], {
[JSON.stringify(data, null, 2)], type: 'text/json',
{ type: 'text/json' } });
);
if (encrypted) { if (encrypted) {
this.downloadData( this.downloadData(
blobData, blobData,
@@ -81,19 +83,16 @@ export class ArchiveManager {
}); });
} }
private async downloadZippedDecryptedItems( private async downloadZippedDecryptedItems(data: BackupFile) {
data: BackupFile
) {
await this.loadZip(); await this.loadZip();
const items = data.items; const items = data.items;
this.zip.createWriter( this.zip.createWriter(
new this.zip.BlobWriter('application/zip'), new this.zip.BlobWriter('application/zip'),
async (zipWriter: any) => { async (zipWriter: any) => {
await new Promise((resolve) => { await new Promise((resolve) => {
const blob = new Blob( const blob = new Blob([JSON.stringify(data, null, 2)], {
[JSON.stringify(data, null, 2)], type: 'text/plain',
{ type: 'text/plain' } });
);
const fileName = zippableTxtName( const fileName = zippableTxtName(
'Standard Notes Backup and Import File.txt' 'Standard Notes Backup and Import File.txt'
); );
@@ -116,7 +115,8 @@ export class ArchiveManager {
name = ''; name = '';
} }
const blob = new Blob([contents], { type: 'text/plain' }); 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]}`); zippableTxtName(name, `-${item.uuid.split('-')[0]}`);
zipWriter.add(fileName, new this.zip.BlobReader(blob), () => { zipWriter.add(fileName, new this.zip.BlobReader(blob), () => {
index++; index++;
@@ -134,7 +134,9 @@ export class ArchiveManager {
}); });
}; };
nextFile(); nextFile();
}, onerror); },
onerror
);
} }
private hrefForData(data: Blob) { private hrefForData(data: Blob) {

View File

@@ -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;
}
}

View File

@@ -0,0 +1,22 @@
import { UuidString } from "@standardnotes/snjs";
import { action, makeObservable, observable } from "mobx";
export class ActionsMenuState {
hiddenExtensions: Record<UuidString, boolean> = {};
constructor() {
makeObservable(this, {
hiddenExtensions: observable,
toggleExtensionVisibility: action,
reset: action,
});
}
toggleExtensionVisibility = (uuid: UuidString): void => {
this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid];
}
reset = (): void => {
this.hiddenExtensions = {};
}
}

View File

@@ -7,16 +7,18 @@ import {
ContentType, ContentType,
PayloadSource, PayloadSource,
DeinitSource, DeinitSource,
UuidString,
SyncOpStatus,
PrefKey, PrefKey,
SNApplication,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor'; 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 { Bridge } from '@/services/bridge';
import { storage, StorageKey } from '@/services/localStorage'; 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 { export enum AppStateEvent {
TagChanged, TagChanged,
@@ -41,113 +43,6 @@ export enum EventSource {
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>; type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>;
class ActionsMenuState {
hiddenExtensions: Record<UuidString, boolean> = {};
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 { export class AppState {
readonly enableUnfinishedFeatures = readonly enableUnfinishedFeatures =
isDev || location.host.includes('app-dev.standardnotes.org'); isDev || location.host.includes('app-dev.standardnotes.org');
@@ -167,8 +62,8 @@ export class AppState {
readonly actionsMenu = new ActionsMenuState(); readonly actionsMenu = new ActionsMenuState();
readonly noAccountWarning: NoAccountWarningState; readonly noAccountWarning: NoAccountWarningState;
readonly sync = new SyncState(); readonly sync = new SyncState();
readonly searchOptions;
isSessionsModalVisible = false; isSessionsModalVisible = false;
mouseUp = Promise.resolve();
private appEventObserverRemovers: (() => void)[] = []; private appEventObserverRemovers: (() => void)[] = [];
@@ -186,6 +81,10 @@ export class AppState {
application, application,
this.appEventObserverRemovers this.appEventObserverRemovers
); );
this.searchOptions = new SearchOptionsState(
application,
this.appEventObserverRemovers
);
this.addAppEventObserver(); this.addAppEventObserver();
this.streamNotesAndTags(); this.streamNotesAndTags();
this.onVisibilityChange = () => { this.onVisibilityChange = () => {
@@ -196,7 +95,6 @@ export class AppState {
this.notifyEvent(event); this.notifyEvent(event);
}; };
this.registerVisibilityObservers(); this.registerVisibilityObservers();
document.addEventListener('mousedown', this.onMouseDown);
if (this.bridge.appVersion.includes('-beta')) { if (this.bridge.appVersion.includes('-beta')) {
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true; this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true;
@@ -233,16 +131,9 @@ export class AppState {
this.rootScopeCleanup2 = undefined; this.rootScopeCleanup2 = undefined;
} }
document.removeEventListener('visibilitychange', this.onVisibilityChange); document.removeEventListener('visibilitychange', this.onVisibilityChange);
document.removeEventListener('mousedown', this.onMouseDown);
this.onVisibilityChange = undefined; this.onVisibilityChange = undefined;
} }
onMouseDown = (): void => {
this.mouseUp = new Promise((resolve) => {
document.addEventListener('mouseup', () => resolve(), { once: true });
});
};
openSessionsModal() { openSessionsModal() {
this.isSessionsModalVisible = true; this.isSessionsModalVisible = true;
} }

View File

@@ -0,0 +1,6 @@
export {
AppState,
AppStateEvent,
EventSource,
PanelResizedData,
} from './app_state';

View File

@@ -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);
}
}

View File

@@ -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<void> => {
if (this.includeProtectedContents) {
this.includeProtectedContents = false;
} else {
const authorized = await this.application.authorizeSearchingProtectedNotesText();
runInAction(() => {
this.includeProtectedContents = authorized;
});
}
};
}

View File

@@ -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' }
);
}
}
}

View File

@@ -12,6 +12,7 @@
i.icon.ion-plus.add-button i.icon.ion-plus.add-button
.filter-section(role='search') .filter-section(role='search')
input#search-bar.filter-bar( input#search-bar.filter-bar(
type="text"
ng-ref='self.searchBarInput' ng-ref='self.searchBarInput'
ng-focus='self.onSearchInputFocus()' ng-focus='self.onSearchInputFocus()'
ng-blur='self.onSearchInputBlur()', ng-blur='self.onSearchInputBlur()',
@@ -25,21 +26,12 @@
#search-clear-button( #search-clear-button(
ng-click='self.clearFilterText();', ng-click='self.clearFilterText();',
ng-show='self.state.noteFilter.text' ng-show='self.state.noteFilter.text'
aria-role="button"
) ✕ ) ✕
label.sk-panel-row.justify-left.mt-2.animate-slide-in-top( search-options(
ng-if='self.state.searchIsFocused || self.state.searchOptionsAreFocused || self.state.authorizingSearchOptions' class="ml-2 h-20px"
style="padding-bottom: 0" app-state='self.appState'
) )
.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
no-account-warning( no-account-warning(
application='self.application' application='self.application'
app-state='self.appState' app-state='self.appState'

View File

@@ -18,10 +18,11 @@ import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import { import {
PANEL_NAME_NOTES PANEL_NAME_NOTES
} from '@/views/constants'; } from '@/views/constants';
import { autorun, IReactionDisposer } from 'mobx';
type NotesState = { type NotesState = {
panelTitle: string panelTitle: string
notes?: SNNote[] notes: SNNote[]
renderedNotes: SNNote[] renderedNotes: SNNote[]
renderedNotesTags: string[], renderedNotesTags: string[],
sortBy?: string sortBy?: string
@@ -33,11 +34,12 @@ type NotesState = {
hideTags: boolean hideTags: boolean
noteFilter: { noteFilter: {
text: string; text: string;
includeProtectedNoteText: boolean;
} }
searchIsFocused: boolean; searchOptions: {
searchOptionsAreFocused: boolean; includeProtectedContents: boolean;
authorizingSearchOptions: boolean; includeArchived: boolean;
includeTrashed: boolean;
}
mutable: { showMenu: boolean } mutable: { showMenu: boolean }
completedFullSync: boolean completedFullSync: boolean
[PrefKey.TagsPanelWidth]?: number [PrefKey.TagsPanelWidth]?: number
@@ -76,8 +78,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
private searchKeyObserver: any private searchKeyObserver: any
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {} private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
private removeObservers: Array<() => void> = []; private removeObservers: Array<() => void> = [];
private searchBarInput?: JQLite; private appStateObserver?: IReactionDisposer;
private searchOptionsInput?: JQLite;
/* @ngInject */ /* @ngInject */
constructor($timeout: ng.ITimeoutService,) { constructor($timeout: ng.ITimeoutService,) {
@@ -94,6 +95,24 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.onPanelResize = this.onPanelResize.bind(this); this.onPanelResize = this.onPanelResize.bind(this);
window.addEventListener('resize', this.onWindowResize, true); window.addEventListener('resize', this.onWindowResize, true);
this.registerKeyboardShortcuts(); 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() { onWindowResize() {
@@ -112,6 +131,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.nextNoteKeyObserver(); this.nextNoteKeyObserver();
this.previousNoteKeyObserver(); this.previousNoteKeyObserver();
this.searchKeyObserver(); this.searchKeyObserver();
this.appStateObserver?.();
this.newNoteKeyObserver = undefined; this.newNoteKeyObserver = undefined;
this.nextNoteKeyObserver = undefined; this.nextNoteKeyObserver = undefined;
this.previousNoteKeyObserver = undefined; this.previousNoteKeyObserver = undefined;
@@ -119,10 +139,6 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
super.deinit(); super.deinit();
} }
getState() {
return this.state as NotesState;
}
async setNotesState(state: Partial<NotesState>) { async setNotesState(state: Partial<NotesState>) {
return this.setState(state); return this.setState(state);
} }
@@ -135,14 +151,15 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
mutable: { showMenu: false }, mutable: { showMenu: false },
noteFilter: { noteFilter: {
text: '', text: '',
includeProtectedNoteText: false },
searchOptions: {
includeArchived: false,
includeProtectedContents: false,
includeTrashed: false,
}, },
panelTitle: '', panelTitle: '',
completedFullSync: false, completedFullSync: false,
hideTags: true, hideTags: true
searchIsFocused: false,
searchOptionsAreFocused: false,
authorizingSearchOptions: false
}; };
} }
@@ -203,7 +220,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
* that may be in progress. This is the sync alternative to `async getMostValidNotes` * that may be in progress. This is the sync alternative to `async getMostValidNotes`
*/ */
private getPossiblyStaleNotes() { private getPossiblyStaleNotes() {
return this.getState().notes!; return this.state.notes;
} }
/** /**
@@ -230,7 +247,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
streamNotesAndTags() { streamNotesAndTags() {
this.removeObservers.push(this.application!.streamItems( this.removeObservers.push(this.application.streamItems(
[ContentType.Note], [ContentType.Note],
async (items) => { async (items) => {
const notes = items as SNNote[]; const notes = items as SNNote[];
@@ -256,7 +273,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
)); ));
this.removeObservers.push(this.application!.streamItems( this.removeObservers.push(this.application.streamItems(
[ContentType.Tag], [ContentType.Tag],
async (items) => { async (items) => {
const tags = items as SNTag[]; const tags = items as SNTag[];
@@ -280,9 +297,9 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
async createNewNote() { async createNewNote() {
let title = `Note ${this.getState().notes!.length + 1}`; let title = `Note ${this.state.notes.length + 1}`;
if (this.isFiltering()) { if (this.isFiltering()) {
title = this.getState().noteFilter.text; title = this.state.noteFilter.text;
} }
await this.appState.createEditor(title); await this.appState.createEditor(title);
await this.flushUI(); await this.flushUI();
@@ -293,21 +310,21 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.resetScrollPosition(); this.resetScrollPosition();
this.setShowMenuFalse(); this.setShowMenuFalse();
await this.setNoteFilterText(''); await this.setNoteFilterText('');
this.application!.getDesktopService().searchText(); this.application.getDesktopService().searchText();
this.resetPagination(); this.resetPagination();
/* Capture db load state before beginning reloadNotes, /* Capture db load state before beginning reloadNotes,
since this status may change during reload */ since this status may change during reload */
const dbLoaded = this.application!.isDatabaseLoaded(); const dbLoaded = this.application.isDatabaseLoaded();
this.reloadNotesDisplayOptions(); this.reloadNotesDisplayOptions();
await this.reloadNotes(); await this.reloadNotes();
if (this.getState().notes!.length > 0) { if (this.state.notes.length > 0) {
this.selectFirstNote(); this.selectFirstNote();
} else if (dbLoaded) { } else if (dbLoaded) {
if ( if (
this.activeEditorNote && this.activeEditorNote &&
!this.getState().notes!.includes(this.activeEditorNote!) !this.state.notes.includes(this.activeEditorNote!)
) { ) {
this.appState.closeActiveEditor(); this.appState.closeActiveEditor();
} }
@@ -323,7 +340,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
async removeNoteFromList(note: SNNote) { async removeNoteFromList(note: SNNote) {
const notes = this.getState().notes!; const notes = this.state.notes;
removeFromArray(notes, note); removeFromArray(notes, note);
await this.setNotesState({ await this.setNotesState({
notes: notes, notes: notes,
@@ -343,23 +360,37 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
*/ */
private reloadNotesDisplayOptions() { private reloadNotesDisplayOptions() {
const tag = this.appState.selectedTag; 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({ const criteria = NotesDisplayCriteria.Create({
sortProperty: this.state.sortBy! as CollectionSort, sortProperty: this.state.sortBy as CollectionSort,
sortDirection: this.state.sortReverse! ? 'asc' : 'dsc', sortDirection: this.state.sortReverse ? 'asc' : 'dsc',
tags: tag ? [tag] : [], tags: tag ? [tag] : [],
includeArchived: this.getState().showArchived!, includeArchived,
includePinned: !this.getState().hidePinned!, includeTrashed,
includePinned: !this.state.hidePinned,
searchQuery: { searchQuery: {
query: searchText ?? '', query: searchText,
includeProtectedNoteText: this.state.noteFilter.includeProtectedNoteText includeProtectedNoteText: this.state.searchOptions.includeProtectedContents
} }
}); });
this.application!.setNotesDisplayCriteria(criteria); this.application.setNotesDisplayCriteria(criteria);
} }
private get selectedTag() { private get selectedTag() {
return this.application!.getAppState().getSelectedTag(); return this.application.getAppState().getSelectedTag();
} }
private async performReloadNotes() { private async performReloadNotes() {
@@ -411,7 +442,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
setShowMenuFalse() { setShowMenuFalse() {
this.setNotesState({ this.setNotesState({
mutable: { mutable: {
...this.getState().mutable, ...this.state.mutable,
showMenu: false showMenu: false
} }
}); });
@@ -420,19 +451,19 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
async handleEditorChange() { async handleEditorChange() {
const activeNote = this.appState.getActiveEditor()?.note; const activeNote = this.appState.getActiveEditor()?.note;
if (activeNote && activeNote.conflictOf) { if (activeNote && activeNote.conflictOf) {
this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => { this.application.changeAndSaveItem(activeNote.uuid, (mutator) => {
mutator.conflictOf = undefined; mutator.conflictOf = undefined;
}); });
} }
if (this.isFiltering()) { if (this.isFiltering()) {
this.application!.getDesktopService().searchText(this.getState().noteFilter.text); this.application.getDesktopService().searchText(this.state.noteFilter.text);
} }
} }
async reloadPreferences() { async reloadPreferences() {
const viewOptions = {} as NotesState; const viewOptions = {} as NotesState;
const prevSortValue = this.getState().sortBy; const prevSortValue = this.state.sortBy;
let sortBy = this.application!.getPreference( let sortBy = this.application.getPreference(
PrefKey.SortNotesBy, PrefKey.SortNotesBy,
CollectionSort.CreatedAt CollectionSort.CreatedAt
); );
@@ -444,23 +475,23 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
sortBy = CollectionSort.UpdatedAt; sortBy = CollectionSort.UpdatedAt;
} }
viewOptions.sortBy = sortBy; viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.application!.getPreference( viewOptions.sortReverse = this.application.getPreference(
PrefKey.SortNotesReverse, PrefKey.SortNotesReverse,
false false
); );
viewOptions.showArchived = this.application!.getPreference( viewOptions.showArchived = this.application.getPreference(
PrefKey.NotesShowArchived, PrefKey.NotesShowArchived,
false false
); );
viewOptions.hidePinned = this.application!.getPreference( viewOptions.hidePinned = this.application.getPreference(
PrefKey.NotesHidePinned, PrefKey.NotesHidePinned,
false false
); );
viewOptions.hideNotePreview = this.application!.getPreference( viewOptions.hideNotePreview = this.application.getPreference(
PrefKey.NotesHideNotePreview, PrefKey.NotesHideNotePreview,
false false
); );
viewOptions.hideDate = this.application!.getPreference( viewOptions.hideDate = this.application.getPreference(
PrefKey.NotesHideDate, PrefKey.NotesHideDate,
false false
); );
@@ -468,7 +499,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
PrefKey.NotesHideTags, PrefKey.NotesHideTags,
true, true,
); );
const state = this.getState(); const state = this.state;
const displayOptionsChanged = ( const displayOptionsChanged = (
viewOptions.sortBy !== state.sortBy || viewOptions.sortBy !== state.sortBy ||
viewOptions.sortReverse !== state.sortReverse || viewOptions.sortReverse !== state.sortReverse ||
@@ -490,13 +521,13 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
reloadPanelWidth() { reloadPanelWidth() {
const width = this.application!.getPreference( const width = this.application.getPreference(
PrefKey.NotesPanelWidth PrefKey.NotesPanelWidth
); );
if (width && this.panelPuppet!.ready) { if (width && this.panelPuppet!.ready) {
this.panelPuppet!.setWidth!(width); this.panelPuppet!.setWidth!(width);
if (this.panelPuppet!.isCollapsed!()) { if (this.panelPuppet!.isCollapsed!()) {
this.application!.getAppState().panelDidResize( this.application.getAppState().panelDidResize(
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
this.panelPuppet!.isCollapsed!() this.panelPuppet!.isCollapsed!()
); );
@@ -510,11 +541,11 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
__: boolean, __: boolean,
isCollapsed: boolean isCollapsed: boolean
) { ) {
this.application!.setPreference( this.application.setPreference(
PrefKey.NotesPanelWidth, PrefKey.NotesPanelWidth,
newWidth newWidth
); );
this.application!.getAppState().panelDidResize( this.application.getAppState().panelDidResize(
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
isCollapsed isCollapsed
); );
@@ -524,7 +555,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.notesToDisplay += this.pageSize; this.notesToDisplay += this.pageSize;
this.reloadNotes(); this.reloadNotes();
if (this.searchSubmitted) { 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<unknown, NotesState> {
reloadPanelTitle() { reloadPanelTitle() {
let title; let title;
if (this.isFiltering()) { if (this.isFiltering()) {
const resultCount = this.getState().notes!.length; const resultCount = this.state.notes.length;
title = `${resultCount} search results`; title = `${resultCount} search results`;
} else if (this.appState.selectedTag) { } else if (this.appState.selectedTag) {
title = `${this.appState.selectedTag.title}`; title = `${this.appState.selectedTag.title}`;
@@ -555,20 +586,20 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
optionsSubtitle() { optionsSubtitle() {
let base = ""; let base = "";
if (this.getState().sortBy === CollectionSort.CreatedAt) { if (this.state.sortBy === CollectionSort.CreatedAt) {
base += " Date Added"; base += " Date Added";
} else if (this.getState().sortBy === CollectionSort.UpdatedAt) { } else if (this.state.sortBy === CollectionSort.UpdatedAt) {
base += " Date Modified"; base += " Date Modified";
} else if (this.getState().sortBy === CollectionSort.Title) { } else if (this.state.sortBy === CollectionSort.Title) {
base += " Title"; base += " Title";
} }
if (this.getState().showArchived) { if (this.state.showArchived) {
base += " | + Archived"; base += " | + Archived";
} }
if (this.getState().hidePinned) { if (this.state.hidePinned) {
base += " | Pinned"; base += " | Pinned";
} }
if (this.getState().sortReverse) { if (this.state.sortReverse) {
base += " | Reversed"; base += " | Reversed";
} }
return base; return base;
@@ -628,13 +659,8 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.noteFlags[note.uuid] = flags; this.noteFlags[note.uuid] = flags;
} }
displayableNotes() {
return this.getState().notes!;
}
getFirstNonProtectedNote() { getFirstNonProtectedNote() {
const displayableNotes = this.displayableNotes(); return this.state.notes.find(note => !note.protected);
return displayableNotes.find(note => !note.protected);
} }
selectFirstNote() { selectFirstNote() {
@@ -645,9 +671,9 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
selectNextNote() { selectNextNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.state.notes;
const currentIndex = displayableNotes.findIndex((candidate) => { const currentIndex = displayableNotes.findIndex((candidate) => {
return candidate.uuid === this.activeEditorNote!.uuid; return candidate.uuid === this.activeEditorNote.uuid;
}); });
if (currentIndex + 1 < displayableNotes.length) { if (currentIndex + 1 < displayableNotes.length) {
this.selectNote(displayableNotes[currentIndex + 1]); this.selectNote(displayableNotes[currentIndex + 1]);
@@ -664,7 +690,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
selectPreviousNote() { selectPreviousNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.state.notes;
const currentIndex = displayableNotes.indexOf(this.activeEditorNote!); const currentIndex = displayableNotes.indexOf(this.activeEditorNote!);
if (currentIndex - 1 >= 0) { if (currentIndex - 1 >= 0) {
this.selectNote(displayableNotes[currentIndex - 1]); this.selectNote(displayableNotes[currentIndex - 1]);
@@ -675,14 +701,14 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
isFiltering() { isFiltering() {
return this.getState().noteFilter.text && return this.state.noteFilter.text &&
this.getState().noteFilter.text.length > 0; this.state.noteFilter.text.length > 0;
} }
async setNoteFilterText(text: string) { async setNoteFilterText(text: string) {
await this.setNotesState({ await this.setNotesState({
noteFilter: { noteFilter: {
...this.getState().noteFilter, ...this.state.noteFilter,
text: text text: text
} }
}); });
@@ -703,72 +729,8 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
await this.reloadNotes(); 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() { async onSearchInputBlur() {
await this.appState.mouseUp; this.appState.searchOptions.refreshIncludeProtectedContents();
/**
* 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,
});
} }
onFilterEnter() { onFilterEnter() {
@@ -778,7 +740,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
* enter before highlighting desktop search results. * enter before highlighting desktop search results.
*/ */
this.searchSubmitted = true; this.searchSubmitted = true;
this.application!.getDesktopService().searchText(this.getState().noteFilter.text); this.application.getDesktopService().searchText(this.state.noteFilter.text);
} }
selectedMenuItem() { selectedMenuItem() {
@@ -786,7 +748,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
togglePrefKey(key: PrefKey) { togglePrefKey(key: PrefKey) {
this.application!.setPreference( this.application.setPreference(
key, key,
!this.state[key] !this.state[key]
); );
@@ -806,14 +768,14 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
toggleReverseSort() { toggleReverseSort() {
this.selectedMenuItem(); this.selectedMenuItem();
this.application!.setPreference( this.application.setPreference(
PrefKey.SortNotesReverse, PrefKey.SortNotesReverse,
!this.getState().sortReverse !this.state.sortReverse
); );
} }
setSortBy(type: CollectionSort) { setSortBy(type: CollectionSort) {
this.application!.setPreference( this.application.setPreference(
PrefKey.SortNotesBy, PrefKey.SortNotesBy,
type type
); );
@@ -829,7 +791,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
* use Control modifier as well. These rules don't apply to desktop, but * use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent. * probably better to be consistent.
*/ */
this.newNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: 'n', key: 'n',
modifiers: [ modifiers: [
KeyboardModifier.Meta, KeyboardModifier.Meta,
@@ -841,7 +803,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
}); });
this.nextNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKey.Down, key: KeyboardKey.Down,
elements: [ elements: [
document.body, document.body,
@@ -856,7 +818,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
}); });
this.previousNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ this.previousNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKey.Up, key: KeyboardKey.Up,
element: document.body, element: document.body,
onKeyDown: () => { onKeyDown: () => {
@@ -864,7 +826,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
} }
}); });
this.searchKeyObserver = this.application!.getKeyboardService().addKeyObserver({ this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: "f", key: "f",
modifiers: [ modifiers: [
KeyboardModifier.Meta, KeyboardModifier.Meta,

View File

@@ -53,6 +53,8 @@
height: 28px; height: 28px;
margin-top: 14px; margin-top: 14px;
position: relative; position: relative;
display: flex;
align-items: center;
.filter-bar { .filter-bar {
background-color: var(--sn-stylekit-contrast-background-color); background-color: var(--sn-stylekit-contrast-background-color);
@@ -82,7 +84,8 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
right: 8px; right: 32px;
cursor: pointer;
transition: background-color 0.15s linear; transition: background-color 0.15s linear;

View File

@@ -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%);
}
}

View File

@@ -183,9 +183,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
grid-column-end: 3; grid-column-end: 3;
} }
.color-neutral {
color: var(--sn-stylekit-neutral-color)
}
.hover\:color-info:hover { .hover\:color-info:hover {
color: var(--sn-stylekit-info-color) color: var(--sn-stylekit-info-color)
} }

View File

@@ -1,3 +1,4 @@
@import "sn-stylekit/dist/stylekit";
@import "main"; @import "main";
@import "ui"; @import "ui";
@import "footer"; @import "footer";
@@ -11,3 +12,4 @@
@import "ionicons"; @import "ionicons";
@import "reach-sub"; @import "reach-sub";
@import "sessions-modal"; @import "sessions-modal";
@import "sn";

View File

@@ -24,6 +24,8 @@
"@babel/plugin-transform-react-jsx": "^7.12.16", "@babel/plugin-transform-react-jsx": "^7.12.16",
"@babel/preset-env": "^7.12.16", "@babel/preset-env": "^7.12.16",
"@babel/preset-typescript": "^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", "@svgr/webpack": "^5.5.0",
"@types/angular": "^1.8.0", "@types/angular": "^1.8.0",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
@@ -53,19 +55,20 @@
"pug-loader": "^2.4.0", "pug-loader": "^2.4.0",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"sn-stylekit": "github:standardnotes/StyleKit#1ec13454cc6d8cf97651263a039de8872be527b8", "sn-stylekit": "github:standardnotes/StyleKit#c88aafa634c1e40d2b8f6e474f44a67805f9c172",
"ts-loader": "^8.0.17", "ts-loader": "^8.0.17",
"typescript": "^4.1.5", "typescript": "^4.1.5",
"typescript-eslint": "0.0.1-alpha.0", "typescript-eslint": "0.0.1-alpha.0",
"webpack": "^4.44.1", "webpack": "^4.44.1",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-merge": "^4.2.2" "webpack-merge": "^5.7.3"
}, },
"dependencies": { "dependencies": {
"@bugsnag/js": "^7.6.0", "@bugsnag/js": "^7.6.0",
"@reach/alert": "^0.13.0", "@reach/alert": "^0.13.0",
"@reach/alert-dialog": "^0.13.0", "@reach/alert-dialog": "^0.13.0",
"@reach/checkbox": "^0.13.2",
"@reach/dialog": "^0.13.0", "@reach/dialog": "^0.13.0",
"@standardnotes/sncrypto-web": "^1.2.10", "@standardnotes/sncrypto-web": "^1.2.10",
"@standardnotes/snjs": "^2.0.70", "@standardnotes/snjs": "^2.0.70",

View File

@@ -1,4 +1,4 @@
const merge = require('webpack-merge'); const { merge } = require('webpack-merge');
const config = require('./webpack.config.js'); const config = require('./webpack.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');

View File

@@ -1,7 +1,9 @@
const merge = require('webpack-merge'); const { merge } = require('webpack-merge');
const config = require('./webpack.config.js'); const config = require('./webpack.config.js');
module.exports = (env, argv) => merge(config(env, argv), { module.exports = (env, argv) => {
return merge(config(env, argv), {
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: 'source-map',
}); });
};

104
yarn.lock
View File

@@ -1810,6 +1810,33 @@
"@reach/utils" "0.13.0" "@reach/utils" "0.13.0"
tslib "^2.0.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": "@reach/dialog@0.13.0", "@reach/dialog@^0.13.0":
version "0.13.0" version "0.13.0"
resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.13.0.tgz#2110725c3b8a3c64685834cdc9f3ce5c15617809" resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.13.0.tgz#2110725c3b8a3c64685834cdc9f3ce5c15617809"
@@ -1822,6 +1849,25 @@
react-remove-scroll "^2.4.1" react-remove-scroll "^2.4.1"
tslib "^2.0.0" 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": "@reach/portal@0.13.0":
version "0.13.0" version "0.13.0"
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.13.0.tgz#bed220d41097deb1454a7928b22529ba10d3ea2b" resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.13.0.tgz#bed220d41097deb1454a7928b22529ba10d3ea2b"
@@ -1839,6 +1885,24 @@
tslib "^2.0.0" tslib "^2.0.0"
warning "^4.0.3" 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": "@reach/visually-hidden@0.13.0":
version "0.13.0" version "0.13.0"
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.13.0.tgz#cace36d9bb80ffb797374fcaea989391b881038f" resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.13.0.tgz#cace36d9bb80ffb797374fcaea989391b881038f"
@@ -1846,6 +1910,14 @@
dependencies: dependencies:
tslib "^2.0.0" 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": "@standardnotes/auth@^2.0.0":
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-2.0.0.tgz#93f633fd40855f87843f911109e92b29dcbc5a04" resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-2.0.0.tgz#93f633fd40855f87843f911109e92b29dcbc5a04"
@@ -2305,6 +2377,11 @@
"@webassemblyjs/wast-parser" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2" "@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": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" 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" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
"sn-stylekit@github:standardnotes/StyleKit#1ec13454cc6d8cf97651263a039de8872be527b8": "sn-stylekit@github:standardnotes/StyleKit#c88aafa634c1e40d2b8f6e474f44a67805f9c172":
version "3.0.1" version "4.0.0"
resolved "https://codeload.github.com/standardnotes/StyleKit/tar.gz/1ec13454cc6d8cf97651263a039de8872be527b8" resolved "https://codeload.github.com/standardnotes/StyleKit/tar.gz/c88aafa634c1e40d2b8f6e474f44a67805f9c172"
snapdragon-node@^2.0.1: snapdragon-node@^2.0.1:
version "2.1.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" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== 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: tsutils@^3.17.1:
version "3.17.1" version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" 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" ansi-colors "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
webpack-merge@^4.2.2: webpack-merge@^5.7.3:
version "4.2.2" version "5.7.3"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213"
integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==
dependencies: 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: webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
version "1.4.3" version "1.4.3"
@@ -8822,6 +8905,11 @@ wide-align@^1.1.0:
dependencies: dependencies:
string-width "^1.0.2 || 2" 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: with@^7.0.0:
version "7.0.2" version "7.0.2"
resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"