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:
3
app/assets/icons/ic_tune.svg
Normal file
3
app/assets/icons/ic_tune.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.5 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 |
@@ -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]);
|
||||||
|
|||||||
112
app/assets/javascripts/components/SearchOptions.tsx
Normal file
112
app/assets/javascripts/components/SearchOptions.tsx
Normal 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);
|
||||||
48
app/assets/javascripts/components/Switch.tsx
Normal file
48
app/assets/javascripts/components/Switch.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
6
app/assets/javascripts/ui_models/app_state/index.ts
Normal file
6
app/assets/javascripts/ui_models/app_state/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export {
|
||||||
|
AppState,
|
||||||
|
AppStateEvent,
|
||||||
|
EventSource,
|
||||||
|
PanelResizedData,
|
||||||
|
} from './app_state';
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
36
app/assets/javascripts/ui_models/app_state/sync_state.ts
Normal file
36
app/assets/javascripts/ui_models/app_state/sync_state.ts
Normal 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' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
59
app/assets/stylesheets/_sn.scss
Normal file
59
app/assets/stylesheets/_sn.scss
Normal 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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
mode: 'production',
|
return merge(config(env, argv), {
|
||||||
devtool: 'source-map',
|
mode: 'production',
|
||||||
});
|
devtool: 'source-map',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
104
yarn.lock
104
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user