feat: multiple selected notes panel

This commit is contained in:
Baptiste Grob
2021-04-08 11:30:56 +02:00
parent 0f53361689
commit abfc588368
36 changed files with 542 additions and 128 deletions

View File

@@ -59,6 +59,7 @@ import { SessionsModalDirective } from './components/SessionsModal';
import { NoAccountWarningDirective } from './components/NoAccountWarning';
import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning';
import { SearchOptionsDirective } from './components/SearchOptions';
import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNotes';
function reloadHiddenFirefoxTab(): boolean {
/**
@@ -147,7 +148,8 @@ const startApplication: StartApplication = async function startApplication(
.directive('sessionsModal', SessionsModalDirective)
.directive('noAccountWarning', NoAccountWarningDirective)
.directive('protectedNotePanel', NoProtectionsdNoteWarningDirective)
.directive('searchOptions', SearchOptionsDirective);
.directive('searchOptions', SearchOptionsDirective)
.directive('multipleSelectedNotesPanel', MultipleSelectedNotesDirective);
// Filters
angular.module('app').filter('trusted', ['$sce', trusted]);

View File

@@ -0,0 +1,192 @@
import { AppState } from '@/ui_models/app_state';
import VisuallyHidden from '@reach/visually-hidden';
import { toDirective, useCloseOnBlur } from './utils';
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@reach/disclosure';
import MoreIcon from '../../icons/ic-more.svg';
import PencilOffIcon from '../../icons/ic-pencil-off.svg';
import RichTextIcon from '../../icons/ic-text-rich.svg';
import TrashIcon from '../../icons/ic-trash.svg';
import PinIcon from '../../icons/ic-pin.svg';
import UnpinIcon from '../../icons/ic-pin-off.svg';
import ArchiveIcon from '../../icons/ic-archive.svg';
import UnarchiveIcon from '../../icons/ic-unarchive.svg';
import NotesIcon from '../../icons/il-notes.svg';
import { useRef, useState } from 'preact/hooks';
import { Switch } from './Switch';
import { observer } from 'mobx-react-lite';
import { SNApplication } from '@standardnotes/snjs';
type Props = {
application: SNApplication;
appState: AppState;
};
const MultipleSelectedNotes = observer(({ appState }: Props) => {
const count = appState.notes.selectedNotesCount;
const [open, setOpen] = useState(false);
const [optionsPanelPosition, setOptionsPanelPosition] = useState({
top: 0,
right: 0,
});
const buttonRef = useRef<HTMLButtonElement>();
const panelRef = useRef<HTMLDivElement>();
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef, setOpen);
const notes = Object.values(appState.notes.selectedNotes);
const hidePreviews = !notes.some((note) => !note.hidePreview);
const locked = !notes.some((note) => !note.locked);
const archived = !notes.some((note) => !note.archived);
const trashed = !notes.some((note) => !note.trashed);
const pinned = !notes.some((note) => !note.pinned);
const iconClass = 'fill-current color-neutral mr-2.5';
const buttonClass =
'flex items-center border-0 capitalize focus:inner-ring-info ' +
'cursor-pointer hover:bg-contrast color-text bg-transparent h-10 px-3 ' +
'text-left';
return (
<div className="flex flex-col h-full items-center">
<div className="flex items-center justify-between p-4 w-full">
<h1 className="text-3xl m-0">{count} selected notes</h1>
<Disclosure
open={open}
onChange={() => {
const rect = buttonRef.current.getBoundingClientRect();
setOptionsPanelPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
});
setOpen((prevOpen) => !prevOpen);
}}
>
<DisclosureButton
onKeyUp={(event) => {
if (event.key === 'Escape') {
setOpen(false);
}
}}
onBlur={closeOnBlur}
ref={buttonRef}
className={
'bg-transparent border-solid border-1 border-gray-300 ' +
'cursor-pointer w-32px h-32px rounded-full p-0 ' +
'flex justify-center items-center'
}
>
<VisuallyHidden>Actions</VisuallyHidden>
<MoreIcon className="fill-current block" />
</DisclosureButton>
<DisclosurePanel
onKeyUp={(event) => {
if (event.key === 'Escape') {
setOpen(false);
buttonRef.current.focus();
}
}}
ref={panelRef}
style={{
...optionsPanelPosition,
}}
className="sn-dropdown sn-dropdown-anchor-right flex flex-col py-2 select-none"
>
<Switch
onBlur={closeOnBlur}
className="h-10"
checked={locked}
onChange={() => {
appState.notes.setLockSelectedNotes(!locked);
}}
>
<span className="capitalize flex items-center">
<PencilOffIcon className={iconClass} />
Prevent editing
</span>
</Switch>
<Switch
onBlur={closeOnBlur}
className="h-10"
checked={!hidePreviews}
onChange={() => {
appState.notes.setHideSelectedNotePreviews(!hidePreviews);
}}
>
<span className="capitalize flex items-center">
<RichTextIcon className={iconClass} />
Show Preview
</span>
</Switch>
<div className="h-1px my-2.5 bg-secondary-contrast"></div>
<button
onBlur={closeOnBlur}
className={buttonClass}
onClick={() => {
appState.notes.setPinSelectedNotes(!pinned);
}}
>
{pinned ? (
<>
<UnpinIcon className={iconClass} />
Unpin notes
</>
) : (
<>
<PinIcon className={iconClass} />
Pin notes
</>
)}
</button>
<button
onBlur={closeOnBlur}
className={buttonClass}
onClick={() => {
appState.notes.setArchiveSelectedNotes(!archived);
}}
>
{archived ? (
<>
<UnarchiveIcon className={iconClass} />
Unarchive
</>
) : (
<>
<ArchiveIcon className={iconClass} />
Archive
</>
)}
</button>
<button
onBlur={closeOnBlur}
className={buttonClass}
onClick={async () => {
setLockCloseOnBlur(true);
await appState.notes.setTrashSelectedNotes(!trashed);
setLockCloseOnBlur(false);
}}
>
<TrashIcon className={iconClass} />
{trashed ? 'Restore' : 'Move to trash'}
</button>
</DisclosurePanel>
</Disclosure>
</div>
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
<NotesIcon className="block" />
<h2 className="text-2xl m-0 text-center mt-4">
{count} selected notes
</h2>
<p className="text-lg mt-2 text-center">
Actions will be performed on all selected notes.
</p>
</div>
</div>
);
});
export const MultipleSelectedNotesDirective = toDirective<Props>(
MultipleSelectedNotes
);

View File

@@ -1,13 +1,12 @@
import { toDirective, useAutorunValue } from './utils';
import Close from '../../icons/ic_close.svg';
import { toDirective } from './utils';
import Close from '../../icons/ic-close.svg';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
type Props = { appState: AppState };
function NoAccountWarning({ appState }: Props) {
const canShow = useAutorunValue(() => appState.noAccountWarning.show, [
appState,
]);
const NoAccountWarning = observer(({ appState }: Props) => {
const canShow = appState.noAccountWarning.show;
if (!canShow) {
return null;
}
@@ -39,6 +38,6 @@ function NoAccountWarning({ appState }: Props) {
</button>
</div>
);
}
});
export const NoAccountWarningDirective = toDirective<Props>(NoAccountWarning);

View File

@@ -1,5 +1,5 @@
import { AppState } from '@/ui_models/app_state';
import { toDirective, useAutorunValue } from './utils';
import { toDirective, useCloseOnBlur } from './utils';
import { useRef, useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application';
import VisuallyHidden from '@reach/visually-hidden';
@@ -10,54 +10,35 @@ import {
} from '@reach/disclosure';
import { FocusEvent } from 'react';
import { Switch } from './Switch';
import TuneIcon from '../../icons/ic_tune.svg';
import TuneIcon from '../../icons/ic-tune.svg';
import { observer } from 'mobx-react-lite';
type Props = {
appState: AppState;
application: WebApplication;
};
function SearchOptions({ appState }: Props) {
const SearchOptions = observer(({ appState }: Props) => {
const { searchOptions } = appState;
const {
includeProtectedContents,
includeArchived,
includeTrashed,
} = useAutorunValue(
() => ({
includeProtectedContents: searchOptions.includeProtectedContents,
includeArchived: searchOptions.includeArchived,
includeTrashed: searchOptions.includeTrashed,
}),
[searchOptions]
);
const [
togglingIncludeProtectedContents,
setTogglingIncludeProtectedContents,
] = useState(false);
async function toggleIncludeProtectedContents() {
setTogglingIncludeProtectedContents(true);
try {
await searchOptions.toggleIncludeProtectedContents();
} finally {
setTogglingIncludeProtectedContents(false);
}
}
} = searchOptions;
const [open, setOpen] = useState(false);
const [optionsPanelTop, setOptionsPanelTop] = useState(0);
const buttonRef = useRef<HTMLButtonElement>();
const panelRef = useRef<HTMLDivElement>();
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef, setOpen);
function closeOnBlur(event: FocusEvent<HTMLElement>) {
if (
!togglingIncludeProtectedContents &&
!panelRef.current.contains(event.relatedTarget as Node)
) {
setOpen(false);
async function toggleIncludeProtectedContents() {
setLockCloseOnBlur(true);
try {
await searchOptions.toggleIncludeProtectedContents();
} finally {
setLockCloseOnBlur(false);
}
}
@@ -86,6 +67,7 @@ function SearchOptions({ appState }: Props) {
className="sn-dropdown sn-dropdown-anchor-right grid gap-2 py-2"
>
<Switch
className="h-10"
checked={includeProtectedContents}
onChange={toggleIncludeProtectedContents}
onBlur={closeOnBlur}
@@ -93,6 +75,7 @@ function SearchOptions({ appState }: Props) {
<p className="capitalize">Include protected contents</p>
</Switch>
<Switch
className="h-10"
checked={includeArchived}
onChange={searchOptions.toggleIncludeArchived}
onBlur={closeOnBlur}
@@ -100,6 +83,7 @@ function SearchOptions({ appState }: Props) {
<p className="capitalize">Include archived notes</p>
</Switch>
<Switch
className="h-10"
checked={includeTrashed}
onChange={searchOptions.toggleIncludeTrashed}
onBlur={closeOnBlur}
@@ -109,6 +93,6 @@ function SearchOptions({ appState }: Props) {
</DisclosurePanel>
</Disclosure>
);
}
});
export const SearchOptionsDirective = toDirective<Props>(SearchOptions);

View File

@@ -15,8 +15,9 @@ import {
AlertDialogDescription,
AlertDialogLabel,
} from '@reach/alert-dialog';
import { toDirective, useAutorunValue } from './utils';
import { toDirective } from './utils';
import { WebApplication } from '@/ui_models/application';
import { observer } from 'mobx-react-lite';
type Session = RemoteSession & {
revoking?: true;
@@ -242,16 +243,12 @@ const SessionsModal: FunctionComponent<{
const Sessions: FunctionComponent<{
appState: AppState;
application: WebApplication;
}> = ({ appState, application }) => {
const showModal = useAutorunValue(() => appState.isSessionsModalVisible, [
appState,
]);
if (showModal) {
}> = observer(({ appState, application }) => {
if (appState.isSessionsModalVisible) {
return <SessionsModal application={application} appState={appState} />;
} else {
return null;
}
};
});
export const SessionsModalDirective = toDirective(Sessions);

View File

@@ -11,6 +11,7 @@ import '@reach/checkbox/styles.css';
export type SwitchProps = HTMLProps<HTMLInputElement> & {
checked?: boolean;
onChange: (checked: boolean) => void;
className?: string;
children: ComponentChildren;
};
@@ -19,8 +20,9 @@ export const Switch: FunctionalComponent<SwitchProps> = (
) => {
const [checkedState, setChecked] = useState(props.checked || false);
const checked = props.checked ?? checkedState;
const className = props.className ?? '';
return (
<label className="sn-component flex justify-between items-center cursor-pointer hover:bg-contrast py-2 px-3">
<label className={`sn-component flex justify-between items-center cursor-pointer hover:bg-contrast px-3 ${className}`}>
{props.children}
<CustomCheckboxContainer
checked={checked}
@@ -33,6 +35,7 @@ export const Switch: FunctionalComponent<SwitchProps> = (
<CustomCheckboxInput
{...({
...props,
className: undefined,
children: undefined,
} as CustomCheckboxInputProps)}
/>

View File

@@ -1,16 +1,31 @@
import { autorun } from 'mobx';
import { FunctionComponent, h, render } from 'preact';
import { Inputs, useEffect, useState } from 'preact/hooks';
import { StateUpdater, useCallback, useState } from 'preact/hooks';
import { FocusEvent, EventHandler, FocusEventHandler } from 'react';
export function useAutorunValue<T>(query: () => T, inputs: Inputs): T {
const [value, setValue] = useState(query);
useEffect(() => {
return autorun(() => {
setValue(query());
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, inputs);
return value;
/**
* @returns a callback that will close a dropdown if none of its children has
* focus. Must be set as the onBlur callback of children that need to be
* monitored.
*/
export function useCloseOnBlur(
container: { current: HTMLDivElement },
setOpen: (open: boolean) => void
): [(event: { relatedTarget: EventTarget | null }) => void, StateUpdater<boolean>] {
const [locked, setLocked] = useState(false);
return [
useCallback(
function onBlur(event: { relatedTarget: EventTarget | null }) {
if (
!locked &&
!container.current.contains(event.relatedTarget as Node)
) {
setOpen(false);
}
},
[container, setOpen, locked]
),
setLocked,
];
}
export function toDirective<Props>(

View File

@@ -25,10 +25,8 @@ import {
ApplicationEvent,
BackupFile,
ContentType,
Platform,
} from '@standardnotes/snjs';
import { confirmDialog, alertDialog } from '@/services/alertService';
import { autorun, IReactionDisposer } from 'mobx';
import { storage, StorageKey } from '@/services/localStorage';
import {
disableErrorReporting,
@@ -84,8 +82,6 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
public appVersion: string;
/** @template */
private closeFunction?: () => void;
private removeBetaWarningListener?: IReactionDisposer;
private removeSyncObserver?: IReactionDisposer;
private removeProtectionLengthObserver?: () => void;
/* @ngInject */
@@ -152,13 +148,13 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
});
const sync = this.appState.sync;
this.removeSyncObserver = autorun(() => {
this.autorun(() => {
this.setState({
syncInProgress: sync.inProgress,
syncError: sync.errorMessage,
});
});
this.removeBetaWarningListener = autorun(() => {
this.autorun(() => {
this.setState({
showBetaWarning: this.appState.showBetaWarning,
});
@@ -175,8 +171,6 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
}
deinit() {
this.removeSyncObserver?.();
this.removeBetaWarningListener?.();
this.removeProtectionLengthObserver?.();
super.deinit();
}

View File

@@ -5,7 +5,6 @@ import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { SNItem, Action, SNActionsExtension, UuidString } from '@standardnotes/snjs';
import { ActionResponse } from '@standardnotes/snjs';
import { ActionsExtensionMutator } from '@standardnotes/snjs';
import { autorun, IReactionDisposer } from 'mobx';
type ActionsMenuScope = {
application: WebApplication
@@ -43,7 +42,6 @@ type ActionsMenuState = {
class ActionsMenuCtrl extends PureViewCtrl<unknown, ActionsMenuState> implements ActionsMenuScope {
application!: WebApplication
item!: SNItem
private removeHiddenExtensionsListener?: IReactionDisposer;
/* @ngInject */
constructor(
@@ -58,17 +56,13 @@ class ActionsMenuCtrl extends PureViewCtrl<unknown, ActionsMenuState> implements
item: this.item
});
this.loadExtensions();
this.removeHiddenExtensionsListener = autorun(() => {
this.autorun(() => {
this.rebuildMenu({
hiddenExtensions: this.appState.actionsMenu.hiddenExtensions
});
});
}
deinit() {
this.removeHiddenExtensionsListener?.();
}
/** @override */
getInitialState() {
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => {

View File

@@ -125,5 +125,7 @@ export const Strings = {
return `Your keys are currently stored in your operating system's ${keychainName}. Adding a passcode prevents even your operating system from reading them.`;
},
protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.',
openAccountMenu: 'Open Account Menu'
openAccountMenu: 'Open Account Menu',
trashNotesTitle: 'Move To Trash',
trashNotesText: 'Are you sure you want to move these notes to the trash?'
};

View File

@@ -83,7 +83,8 @@ export class AppState {
this.application,
async () => {
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
}
},
this.appEventObserverRemovers,
);
this.noAccountWarning = new NoAccountWarningState(
application,

View File

@@ -1,41 +1,81 @@
import { KeyboardModifier } from "@/services/ioService";
import { UuidString, SNNote } from "@standardnotes/snjs";
import { makeObservable, observable, action } from "mobx";
import { WebApplication } from "../application";
import { Editor } from "../editor";
import { confirmDialog } from '@/services/alertService';
import { KeyboardModifier } from '@/services/ioService';
import { Strings } from '@/strings';
import {
UuidString,
SNNote,
NoteMutator,
ContentType,
} from '@standardnotes/snjs';
import {
makeObservable,
observable,
action,
computed,
runInAction,
} from 'mobx';
import { WebApplication } from '../application';
import { Editor } from '../editor';
export class NotesState {
selectedNotes: Record<UuidString, SNNote> = {};
constructor(
private application: WebApplication,
private onActiveEditorChanged: () => Promise<void>
private onActiveEditorChanged: () => Promise<void>,
appEventListeners: (() => void)[]
) {
makeObservable(this, {
selectedNotes: observable,
selectedNotesCount: computed,
selectNote: action,
setHideSelectedNotePreviews: action,
setLockSelectedNotes: action,
});
appEventListeners.push(
application.streamItems(ContentType.Note, (notes) => {
runInAction(() => {
for (const note of notes) {
if (this.selectedNotes[note.uuid]) {
this.selectedNotes[note.uuid] = note as SNNote;
}
}
});
})
);
}
get activeEditor(): Editor | undefined {
return this.application.editorGroup.editors[0];
}
async selectNote(note: SNNote): Promise<void> {
get selectedNotesCount(): number {
return Object.keys(this.selectedNotes).length;
}
async selectNote(uuid: UuidString): Promise<void> {
const note = this.application.findItem(uuid) as SNNote;
if (
this.io.activeModifiers.has(KeyboardModifier.Meta) ||
this.io.activeModifiers.has(KeyboardModifier.Ctrl)
) {
this.selectedNotes[note.uuid] = note;
if (this.selectedNotes[uuid]) {
delete this.selectedNotes[uuid];
} else {
this.selectedNotes[uuid] = note;
}
} else {
this.selectedNotes = {
[note.uuid]: note,
[uuid]: note,
};
await this.openEditor(uuid);
}
await this.openEditor(note.uuid);
}
async openEditor(noteUuid: string): Promise<void> {
private async openEditor(noteUuid: string): Promise<void> {
if (this.activeEditor?.note?.uuid === noteUuid) {
return;
}
@@ -60,6 +100,69 @@ export class NotesState {
}
}
setHideSelectedNotePreviews(hide: boolean): void {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.hidePreview = hide;
},
false
);
}
setLockSelectedNotes(lock: boolean): void {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.locked = lock;
},
false
);
}
async setTrashSelectedNotes(trashed: boolean): Promise<void> {
if (
await confirmDialog({
title: Strings.trashNotesTitle,
text: Strings.trashNotesText,
confirmButtonStyle: 'danger',
})
) {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.trashed = trashed;
},
false
);
runInAction(() => {
this.selectedNotes = {};
});
}
}
setPinSelectedNotes(pinned: boolean): void {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.pinned = pinned;
},
false
);
}
setArchiveSelectedNotes(archived: boolean): void {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.archived = archived;
}
);
runInAction(() => {
this.selectedNotes = {};
});
}
private get io() {
return this.application.io;
}

View File

@@ -7,9 +7,7 @@
)
tags-view(application='self.application')
notes-view(application='self.application')
editor-group-view(
application='self.application'
)
editor-group-view.flex-grow(application='self.application')
footer-view(
ng-if='!self.state.needsUnlock && self.state.ready'

View File

@@ -1185,7 +1185,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
* (and not when our controller is destroyed.)
*/
angular.element(editor).one('$destroy', () => {
this.removeTabObserver();
this.removeTabObserver?.();
this.removeTabObserver = undefined;
});
}

View File

@@ -1,7 +1,14 @@
.flex-grow(
ng-repeat='editor in self.editors'
)
editor-view(
.h-full
multiple-selected-notes-panel.h-full(
app-state='self.appState'
application='self.application'
editor='editor'
ng-if='self.state.showMultipleSelectedNotes'
)
.flex-grow.h-full(
ng-if='!self.state.showMultipleSelectedNotes'
ng-repeat='editor in self.editors'
)
editor-view(
application='self.application'
editor='editor'
)

View File

@@ -2,16 +2,31 @@ import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types';
import template from './editor-group-view.pug';
import { Editor } from '@/ui_models/editor';
import { PureViewCtrl } from '../abstract/pure_view_ctrl';
class EditorGroupViewCtrl {
class EditorGroupViewCtrl extends PureViewCtrl<unknown, {
showMultipleSelectedNotes: boolean
}> {
private application!: WebApplication
public editors: Editor[] = []
/* @ngInject */
constructor($timeout: ng.ITimeoutService,) {
super($timeout);
this.state = {
showMultipleSelectedNotes: false
}
}
$onInit() {
this.application.editorGroup.addChangeObserver(() => {
this.editors = this.application.editorGroup.editors;
});
this.autorun(() => {
this.setState({
showMultipleSelectedNotes: this.appState.notes.selectedNotesCount > 1
});
});
}
}
@@ -20,7 +35,6 @@ export class EditorGroupView extends WebDirective {
super();
this.template = template;
this.controller = EditorGroupViewCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
this.scope = {

View File

@@ -23,7 +23,6 @@ import {
} from '@/strings';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { alertDialog, confirmDialog } from '@/services/alertService';
import { autorun, IReactionDisposer } from 'mobx';
/**
* Disable before production release.
@@ -75,7 +74,6 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
private observerRemovers: Array<() => void> = [];
private completedInitialSync = false;
private showingDownloadStatus = false;
private autorunDisposer?: IReactionDisposer;
/* @ngInject */
constructor(
@@ -103,7 +101,6 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
this.rootScopeListener2 = undefined;
(this.closeAccountMenu as any) = undefined;
(this.toggleSyncResolutionMenu as any) = undefined;
this.autorunDisposer?.();
super.deinit();
}
@@ -115,7 +112,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
});
});
this.loadAccountSwitcherState();
this.autorunDisposer = autorun(() => {
this.autorun(() => {
const showBetaWarning = this.appState.showBetaWarning;
this.showAccountMenu = this.appState.accountMenu.show;
this.setState({

View File

@@ -1,7 +1,7 @@
#notes-column.sn-component.section.notes(aria-label='Notes')
.content
#notes-title-bar.section-title-bar
.p-4.pt-0
.p-4
.section-title-bar-header
.sk-h2.font-semibold.title {{self.state.panelTitle}}
.sk-button.contrast.wide(

View File

@@ -297,7 +297,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
}
selectNote(note: SNNote): Promise<void> {
return this.appState.notes.selectNote(note);
return this.appState.notes.selectNote(note.uuid);
}
async createNewNote() {