Merge branch 'develop' into feature/remove-batch-manager

This commit is contained in:
Antonella Sgarlatta
2021-06-14 15:53:06 -03:00
committed by GitHub
17 changed files with 80 additions and 88 deletions

View File

@@ -47,7 +47,9 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => {
const onFormSubmit = async (event: Event) => { const onFormSubmit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
if (autocompleteSearchQuery !== '') {
await appState.noteTags.createAndAddNewTag(); await appState.noteTags.createAndAddNewTag();
}
}; };
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
@@ -115,8 +117,9 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => {
ref={dropdownRef} ref={dropdownRef}
className={`${tags.length > 0 ? 'w-80' : 'w-70 mr-10'} sn-dropdown flex flex-col py-2 absolute`} className={`${tags.length > 0 ? 'w-80' : 'w-70 mr-10'} sn-dropdown flex flex-col py-2 absolute`}
style={{ maxHeight: dropdownMaxHeight, maxWidth: tagsContainerMaxWidth }} style={{ maxHeight: dropdownMaxHeight, maxWidth: tagsContainerMaxWidth }}
onBlur={closeOnBlur}
> >
<div className="overflow-y-scroll"> <div className="overflow-y-auto">
{autocompleteTagResults.map((tagResult) => ( {autocompleteTagResults.map((tagResult) => (
<AutocompleteTagResult <AutocompleteTagResult
key={tagResult.uuid} key={tagResult.uuid}

View File

@@ -23,9 +23,7 @@ const ConfirmSignoutContainer = observer((props: Props) => {
}); });
const ConfirmSignoutModal = observer(({ application, appState }: Props) => { const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
const [deleteLocalBackups, setDeleteLocalBackups] = useState( const [deleteLocalBackups, setDeleteLocalBackups] = useState(false);
application.hasAccount()
);
const cancelRef = useRef<HTMLButtonElement>(); const cancelRef = useRef<HTMLButtonElement>();
function close() { function close() {

View File

@@ -23,7 +23,7 @@ const NotesContextMenu = observer(({ appState }: Props) => {
return appState.notes.contextMenuOpen ? ( return appState.notes.contextMenuOpen ? (
<div <div
ref={contextMenuRef} ref={contextMenuRef}
className="sn-dropdown max-h-120 max-w-xs flex flex-col py-2 overflow-y-scroll fixed" className="sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
style={{ style={{
...appState.notes.contextMenuPosition, ...appState.notes.contextMenuPosition,
maxHeight: appState.notes.contextMenuMaxHeight, maxHeight: appState.notes.contextMenuMaxHeight,

View File

@@ -159,7 +159,7 @@ export const NotesOptions = observer(
maxHeight: tagsMenuMaxHeight, maxHeight: tagsMenuMaxHeight,
position: 'fixed', position: 'fixed',
}} }}
className="sn-dropdown flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-scroll" className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto"
> >
{appState.tags.tags.map((tag) => ( {appState.tags.tags.map((tag) => (
<button <button

View File

@@ -71,7 +71,8 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
...position, ...position,
maxHeight, maxHeight,
}} }}
className="sn-dropdown sn-dropdown--animated max-h-120 max-w-xs flex flex-col py-2 overflow-y-scroll fixed" className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
onBlur={closeOnBlur}
> >
{open && ( {open && (
<NotesOptions <NotesOptions

View File

@@ -63,7 +63,8 @@ const SearchOptions = observer(({ appState }: Props) => {
style={{ style={{
top: optionsPanelTop, top: optionsPanelTop,
}} }}
className="sn-dropdown sn-dropdown--anchor-right sn-dropdown--animated absolute grid gap-2 py-2" className="sn-dropdown sn-dropdown--anchor-right sn-dropdown--animated min-w-80 absolute grid gap-2 py-2"
onBlur={closeOnBlur}
> >
<Switch <Switch
className="h-10" className="h-10"

View File

@@ -126,7 +126,7 @@ const SessionsModal: FunctionComponent<{
return ( return (
<> <>
<Dialog onDismiss={close} className="sessions-modal"> <Dialog onDismiss={close} className="sessions-modal h-90vh">
<div className="sk-modal-content"> <div className="sk-modal-content">
<div class="sn-component"> <div class="sn-component">
<div class="sk-panel"> <div class="sk-panel">
@@ -145,7 +145,7 @@ const SessionsModal: FunctionComponent<{
</button> </button>
</div> </div>
</div> </div>
<div class="sk-panel-content"> <div class="sk-panel-content overflow-y-auto">
{refreshing ? ( {refreshing ? (
<> <>
<div class="sk-spinner small info"></div> <div class="sk-spinner small info"></div>

View File

@@ -1,10 +1,8 @@
import { ApplicationService } from '@standardnotes/snjs'; import { ApplicationService } from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { AppStateEvent } from '@/ui_models/app_state';
const MILLISECONDS_PER_SECOND = 1000; const MILLISECONDS_PER_SECOND = 1000;
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND; const POLL_INTERVAL = 50;
const LOCK_INTERVAL_NONE = 0; const LOCK_INTERVAL_NONE = 0;
const LOCK_INTERVAL_IMMEDIATE = 1; const LOCK_INTERVAL_IMMEDIATE = 1;
const LOCK_INTERVAL_ONE_MINUTE = 60 * MILLISECONDS_PER_SECOND; const LOCK_INTERVAL_ONE_MINUTE = 60 * MILLISECONDS_PER_SECOND;
@@ -15,37 +13,21 @@ const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
export class AutolockService extends ApplicationService { export class AutolockService extends ApplicationService {
private unsubState?: () => void; private pollInterval: any
private pollFocusInterval: any
private lastFocusState?: 'hidden' | 'visible' private lastFocusState?: 'hidden' | 'visible'
private lockAfterDate?: Date private lockAfterDate?: Date
private lockTimeout?: any
onAppLaunch() { onAppLaunch() {
this.observeVisibility(); if (!isDesktopApplication()) {
this.beginPolling();
}
return super.onAppLaunch(); return super.onAppLaunch();
} }
observeVisibility() {
this.unsubState = (this.application as WebApplication).getAppState().addObserver(
async (eventName) => {
if (eventName === AppStateEvent.WindowDidBlur) {
this.documentVisibilityChanged(false);
} else if (eventName === AppStateEvent.WindowDidFocus) {
this.documentVisibilityChanged(true);
}
}
);
if (!isDesktopApplication()) {
this.beginWebFocusPolling();
}
}
deinit() { deinit() {
this.unsubState?.();
this.cancelAutoLockTimer(); this.cancelAutoLockTimer();
if (this.pollFocusInterval) { if (this.pollInterval) {
clearInterval(this.pollFocusInterval); clearInterval(this.pollInterval);
} }
} }
@@ -85,11 +67,15 @@ export class AutolockService extends ApplicationService {
* Verify document is in focus every so often as visibilitychange event is * Verify document is in focus every so often as visibilitychange event is
* not triggered on a typical window blur event but rather on tab changes. * not triggered on a typical window blur event but rather on tab changes.
*/ */
beginWebFocusPolling() { beginPolling() {
this.pollFocusInterval = setInterval(() => { this.pollInterval = setInterval(async () => {
if (document.hidden) { const locked = await this.application.isLocked();
/** Native event listeners will have fired */ if (
return; !locked &&
this.lockAfterDate &&
new Date() > this.lockAfterDate
) {
this.lockApplication();
} }
const hasFocus = document.hasFocus(); const hasFocus = document.hasFocus();
if (hasFocus && this.lastFocusState === 'hidden') { if (hasFocus && this.lastFocusState === 'hidden') {
@@ -99,7 +85,7 @@ export class AutolockService extends ApplicationService {
} }
/* Save this to compare against next time around */ /* Save this to compare against next time around */
this.lastFocusState = hasFocus ? 'visible' : 'hidden'; this.lastFocusState = hasFocus ? 'visible' : 'hidden';
}, FOCUS_POLL_INTERVAL); }, POLL_INTERVAL);
} }
getAutoLockIntervalOptions() { getAutoLockIntervalOptions() {
@@ -129,14 +115,6 @@ export class AutolockService extends ApplicationService {
async documentVisibilityChanged(visible: boolean) { async documentVisibilityChanged(visible: boolean) {
if (visible) { if (visible) {
const locked = await this.application.isLocked();
if (
!locked &&
this.lockAfterDate &&
new Date() > this.lockAfterDate
) {
this.lockApplication();
}
this.cancelAutoLockTimer(); this.cancelAutoLockTimer();
} else { } else {
this.beginAutoLockTimer(); this.beginAutoLockTimer();
@@ -148,29 +126,15 @@ export class AutolockService extends ApplicationService {
if (interval === LOCK_INTERVAL_NONE) { if (interval === LOCK_INTERVAL_NONE) {
return; return;
} }
/**
* Use a timeout if possible, but if the computer is put to sleep, timeouts won't
* work. Need to set a date as backup. this.lockAfterDate does not need to be
* persisted, as living in memory is sufficient. If memory is cleared, then the
* application will lock anyway.
*/
const addToNow = (seconds: number) => { const addToNow = (seconds: number) => {
const date = new Date(); const date = new Date();
date.setSeconds(date.getSeconds() + seconds); date.setSeconds(date.getSeconds() + seconds);
return date; return date;
}; };
this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND); this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND);
clearTimeout(this.lockTimeout);
this.lockTimeout = setTimeout(() => {
this.cancelAutoLockTimer();
this.lockApplication();
this.lockAfterDate = undefined;
}, interval);
} }
cancelAutoLockTimer() { cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = undefined; this.lockAfterDate = undefined;
this.lockTimeout = undefined;
} }
} }

View File

@@ -142,7 +142,7 @@ export class AppState {
this.noAccountWarning.reset(); this.noAccountWarning.reset();
} }
this.actionsMenu.reset(); this.actionsMenu.reset();
this.unsubApp(); this.unsubApp?.();
this.unsubApp = undefined; this.unsubApp = undefined;
this.observers.length = 0; this.observers.length = 0;
this.appEventObserverRemovers.forEach((remover) => remover()); this.appEventObserverRemovers.forEach((remover) => remover());

View File

@@ -198,9 +198,15 @@ export class NoteTagsState {
async removeTagFromActiveNote(tag: SNTag): Promise<void> { async removeTagFromActiveNote(tag: SNTag): Promise<void> {
const { activeNote } = this; const { activeNote } = this;
if (activeNote) { if (activeNote) {
const descendantTags = this.application.getTagDescendants(tag);
const tagsToRemove = [...descendantTags, tag];
await Promise.all(
tagsToRemove.map(async (tag) => {
await this.application.changeItem(tag.uuid, (mutator) => { await this.application.changeItem(tag.uuid, (mutator) => {
mutator.removeItemAsRelationship(activeNote); mutator.removeItemAsRelationship(activeNote);
}); });
})
);
this.application.sync(); this.application.sync();
this.reloadTags(); this.reloadTags();
} }

View File

@@ -346,13 +346,18 @@ export class NotesState {
async removeTagFromSelectedNotes(tag: SNTag): Promise<void> { async removeTagFromSelectedNotes(tag: SNTag): Promise<void> {
const selectedNotes = Object.values(this.selectedNotes); const selectedNotes = Object.values(this.selectedNotes);
const descendantTags = this.application.getTagDescendants(tag);
const tagsToRemove = [...descendantTags, tag];
await Promise.all(
tagsToRemove.map(async (tag) => {
await this.application.changeItem(tag.uuid, (mutator) => { await this.application.changeItem(tag.uuid, (mutator) => {
for (const note of selectedNotes) { for (const note of selectedNotes) {
mutator.removeItemAsRelationship(note); mutator.removeItemAsRelationship(note);
} }
}); });
})
);
this.application.sync(); this.application.sync();
} }
isTagInSelectedNotes(tag: SNTag): boolean { isTagInSelectedNotes(tag: SNTag): boolean {

View File

@@ -40,8 +40,8 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
} }
deinit(): void { deinit(): void {
this.unsubApp(); this.unsubApp?.();
this.unsubState(); this.unsubState?.();
for (const disposer of this.reactionDisposers) { for (const disposer of this.reactionDisposers) {
disposer(); disposer();
} }

View File

@@ -289,10 +289,10 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
async saveTag($event: Event, tag: SNTag) { async saveTag($event: Event, tag: SNTag) {
($event.target! as HTMLInputElement).blur(); ($event.target! as HTMLInputElement).blur();
if (this.getState().templateTag) {
if (!this.titles[tag.uuid]?.length) { if (!this.titles[tag.uuid]?.length) {
return this.undoCreateTag(tag); return this.undoCreateTag(tag);
} }
if (this.getState().templateTag) {
return this.saveNewTag(); return this.saveNewTag();
} else { } else {
return this.saveTagRename(tag); return this.saveTagRename(tag);
@@ -314,6 +314,9 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
if (newTitle.length === 0) { if (newTitle.length === 0) {
this.titles[tag.uuid] = this.editingOriginalName; this.titles[tag.uuid] = this.editingOriginalName;
this.editingOriginalName = undefined; this.editingOriginalName = undefined;
await this.setState({
editingTag: undefined
});
return; return;
} }
const existingTag = this.application.findTagByTitle(newTitle); const existingTag = this.application.findTagByTitle(newTitle);
@@ -345,6 +348,7 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
this.application.alertService!.alert( this.application.alertService!.alert(
"A tag with this name already exists." "A tag with this name already exists."
); );
this.undoCreateTag(newTag);
return; return;
} }
const insertedTag = await this.application.insertItem(newTag); const insertedTag = await this.application.insertItem(newTag);

View File

@@ -109,6 +109,11 @@
padding-bottom: 0.375rem; padding-bottom: 0.375rem;
} }
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.outline-none { .outline-none {
outline: none; outline: none;
} }
@@ -254,6 +259,10 @@
height: 4.5rem; height: 4.5rem;
} }
.h-90vh {
height: 90vh;
}
.max-h-120 { .max-h-120 {
max-height: 30rem; max-height: 30rem;
} }
@@ -266,8 +275,8 @@
position: fixed; position: fixed;
} }
.overflow-y-scroll { .overflow-y-auto {
overflow-y: scroll; overflow-y: auto;
} }
.overflow-auto { .overflow-auto {
@@ -351,7 +360,6 @@
.sn-dropdown { .sn-dropdown {
@extend .bg-default; @extend .bg-default;
// @extend .min-w-80;
@extend .rounded; @extend .rounded;
@extend .box-shadow; @extend .box-shadow;

View File

@@ -72,6 +72,8 @@
} }
> .title { > .title {
@extend .focus\:outline-none;
@extend .focus\:shadow-none;
width: 80%; width: 80%;
background-color: transparent; background-color: transparent;
font-weight: 600; font-weight: 600;

View File

@@ -1,6 +1,6 @@
{ {
"name": "standard-notes-web", "name": "standard-notes-web",
"version": "3.9.0-beta01", "version": "3.8.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -71,7 +71,7 @@
"@reach/checkbox": "^0.13.2", "@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.6.0", "@standardnotes/snjs": "2.6.3",
"mobx": "^6.1.6", "mobx": "^6.1.6",
"mobx-react-lite": "^3.2.0", "mobx-react-lite": "^3.2.0",
"preact": "^10.5.12" "preact": "^10.5.12"

View File

@@ -1936,10 +1936,10 @@
"@standardnotes/sncrypto-common" "^1.2.7" "@standardnotes/sncrypto-common" "^1.2.7"
libsodium-wrappers "^0.7.8" libsodium-wrappers "^0.7.8"
"@standardnotes/snjs@2.6.0": "@standardnotes/snjs@2.6.3":
version "2.6.0" version "2.6.3"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.6.0.tgz#8ebdfcb0918c308198b38a63d7aa946387b83ac4" resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.6.3.tgz#7677899c050b0616d994423fd4ec9caf03394f35"
integrity sha512-Gb/kAdMtjVlSiQH7pkDzFxKtIrrY43i2hSejO2c+zCviZspiDZPpXLpEhMJ295ow2tluhOf8zfBUda3LMC6oDw== integrity sha512-5pWh+BPVPpd6JlP3avo2puGk9EWUaH0+6Y1fN9rMR8oLZ2oc8Dffiy5S4TLNm8zL4q504oMlm1/ALkwwZpKLEQ==
dependencies: dependencies:
"@standardnotes/auth" "^2.0.0" "@standardnotes/auth" "^2.0.0"
"@standardnotes/sncrypto-common" "^1.2.9" "@standardnotes/sncrypto-common" "^1.2.9"