diff --git a/.env.sample b/.env.sample index 100b7cc5d..03888379e 100644 --- a/.env.sample +++ b/.env.sample @@ -9,7 +9,6 @@ SECRET_KEY_BASE=test APP_HOST=http://localhost:3001 EXTENSIONS_MANAGER_LOCATION=extensions/extensions-manager/dist/index.html -BATCH_MANAGER_LOCATION=extensions/batch-manager/dist/index.min.html SF_DEFAULT_SERVER=http://localhost:3000 SF_NEXT_VERSION_SERVER=http://localhost:3000 @@ -17,7 +16,6 @@ SF_NEXT_VERSION_SERVER=http://localhost:3000 DEV_DEFAULT_SYNC_SERVER=https://sync.standardnotes.org DEV_NEXT_VERSION_SYNC_SERVER=https://api.standardnotes.com DEV_EXTENSIONS_MANAGER_LOCATION=public/extensions/extensions-manager/dist/index.html -DEV_BATCH_MANAGER_LOCATION=public/extensions/batch-manager/dist/index.min.html # NewRelic (Optional) NEW_RELIC_ENABLED=false diff --git a/.gitmodules b/.gitmodules index 86c55fdbd..adb7eaeaa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,4 @@ [submodule "public/extensions/extensions-manager"] path = public/extensions/extensions-manager url = https://github.com/sn-extensions/extensions-manager.git -[submodule "public/extensions/batch-manager"] - path = public/extensions/batch-manager - url = https://github.com/sn-extensions/batch-manager.git + diff --git a/README.md b/README.md index 44f760c09..2e6ea90fa 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,6 @@ The web app makes use of two optional native extensions, which, when running the 1. Set the following environment variables in the .env file: ``` EXTENSIONS_MANAGER_LOCATION=extensions/extensions-manager/dist/index.html - BATCH_MANAGER_LOCATION=extensions/batch-manager/dist/index.min.html ``` You can also set the `SF_DEFAULT_SERVER` and `SF_NEXT_VERSION_SERVER` environment variables to set the default servers for login and registration. diff --git a/app/assets/javascripts/components/AutocompleteTagInput.tsx b/app/assets/javascripts/components/AutocompleteTagInput.tsx index e7d242798..41b77280d 100644 --- a/app/assets/javascripts/components/AutocompleteTagInput.tsx +++ b/app/assets/javascripts/components/AutocompleteTagInput.tsx @@ -102,7 +102,7 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { 0 ? 'w-80' : 'w-70 mr-10'} bg-default text-xs + className={`${tags.length > 0 ? 'w-80' : 'w-70 mr-10'} bg-transparent text-xs color-text no-border h-7 focus:outline-none focus:shadow-none focus:border-bottom`} value={autocompleteSearchQuery} onChange={onSearchQueryChange} diff --git a/app/assets/javascripts/components/NoteTagsContainer.tsx b/app/assets/javascripts/components/NoteTagsContainer.tsx index 8d94cdf42..59baadd52 100644 --- a/app/assets/javascripts/components/NoteTagsContainer.tsx +++ b/app/assets/javascripts/components/NoteTagsContainer.tsx @@ -21,7 +21,7 @@ const NoteTagsContainer = observer(({ appState }: Props) => { return (
{ maxHeight, }} 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 && ( { top: optionsPanelTop, }} className="sn-dropdown sn-dropdown--anchor-right sn-dropdown--animated min-w-80 absolute grid gap-2 py-2" + onBlur={closeOnBlur} > void; - private pollFocusInterval: any + private pollInterval: any private lastFocusState?: 'hidden' | 'visible' private lockAfterDate?: Date - private lockTimeout?: any onAppLaunch() { - this.observeVisibility(); + if (!isDesktopApplication()) { + this.beginPolling(); + } 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() { - this.unsubState?.(); this.cancelAutoLockTimer(); - if (this.pollFocusInterval) { - clearInterval(this.pollFocusInterval); + if (this.pollInterval) { + clearInterval(this.pollInterval); } } @@ -85,11 +67,15 @@ export class AutolockService extends ApplicationService { * 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. */ - beginWebFocusPolling() { - this.pollFocusInterval = setInterval(() => { - if (document.hidden) { - /** Native event listeners will have fired */ - return; + beginPolling() { + this.pollInterval = setInterval(async () => { + const locked = await this.application.isLocked(); + if ( + !locked && + this.lockAfterDate && + new Date() > this.lockAfterDate + ) { + this.lockApplication(); } const hasFocus = document.hasFocus(); if (hasFocus && this.lastFocusState === 'hidden') { @@ -99,7 +85,7 @@ export class AutolockService extends ApplicationService { } /* Save this to compare against next time around */ this.lastFocusState = hasFocus ? 'visible' : 'hidden'; - }, FOCUS_POLL_INTERVAL); + }, POLL_INTERVAL); } getAutoLockIntervalOptions() { @@ -129,14 +115,6 @@ export class AutolockService extends ApplicationService { async documentVisibilityChanged(visible: boolean) { if (visible) { - const locked = await this.application.isLocked(); - if ( - !locked && - this.lockAfterDate && - new Date() > this.lockAfterDate - ) { - this.lockApplication(); - } this.cancelAutoLockTimer(); } else { this.beginAutoLockTimer(); @@ -148,29 +126,15 @@ export class AutolockService extends ApplicationService { if (interval === LOCK_INTERVAL_NONE) { 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 date = new Date(); date.setSeconds(date.getSeconds() + seconds); return date; }; this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND); - clearTimeout(this.lockTimeout); - this.lockTimeout = setTimeout(() => { - this.cancelAutoLockTimer(); - this.lockApplication(); - this.lockAfterDate = undefined; - }, interval); } cancelAutoLockTimer() { - clearTimeout(this.lockTimeout); this.lockAfterDate = undefined; - this.lockTimeout = undefined; } } diff --git a/app/assets/javascripts/services/nativeExtManager.ts b/app/assets/javascripts/services/nativeExtManager.ts index 42e52b6d8..64d728987 100644 --- a/app/assets/javascripts/services/nativeExtManager.ts +++ b/app/assets/javascripts/services/nativeExtManager.ts @@ -8,15 +8,12 @@ import { FillItemContent, ComponentMutator, Copy, - dictToArray -, PayloadContent , ComponentPermission } from '@standardnotes/snjs'; - - + PayloadContent, + ComponentPermission } from '@standardnotes/snjs'; /** A class for handling installation of system extensions */ export class NativeExtManager extends ApplicationService { extManagerId = 'org.standardnotes.extensions-manager'; - batchManagerId = 'org.standardnotes.batch-manager'; /** @override */ async onAppLaunch() { @@ -32,14 +29,6 @@ export class NativeExtManager extends ApplicationService { ]); } - get batchManagerPred() { - const batchMgrId = 'org.standardnotes.batch-manager'; - return SNPredicate.CompoundPredicate([ - new SNPredicate('content_type', '=', ContentType.Component), - new SNPredicate('package_info.identifier', '=', batchMgrId) - ]); - } - get extMgrUrl() { return (window as any)._extensions_manager_location; } @@ -50,9 +39,7 @@ export class NativeExtManager extends ApplicationService { reload() { this.application!.singletonManager!.registerPredicate(this.extManagerPred); - this.application!.singletonManager!.registerPredicate(this.batchManagerPred); this.resolveExtensionsManager(); - this.resolveBatchManager(); } async resolveExtensionsManager() { @@ -132,75 +119,4 @@ export class NativeExtManager extends ApplicationService { } return content; } - - async resolveBatchManager() { - const batchManager = (await this.application!.singletonManager!.findOrCreateSingleton( - this.batchManagerPred, - ContentType.Component, - this.batchManagerTemplateContent() - )) as SNComponent; - let needsSync = false; - if (isDesktopApplication()) { - if (!batchManager.local_url) { - await this.application!.changeItem(batchManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.local_url = this.batchMgrUrl; - }); - needsSync = true; - } - } else { - if (!batchManager.hosted_url) { - await this.application!.changeItem(batchManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.hosted_url = this.batchMgrUrl; - }); - needsSync = true; - } - } - // Handle addition of SN|ExtensionRepo permission - const permissions = Copy(batchManager!.permissions) as ComponentPermission[]; - const permission = permissions.find((p) => { - return p.name === ComponentAction.StreamItems; - }); - if (permission && !permission.content_types!.includes(ContentType.ExtensionRepo)) { - permission.content_types!.push(ContentType.ExtensionRepo); - await this.application!.changeItem(batchManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.permissions = permissions; - }); - needsSync = true; - } - if (needsSync) { - this.application!.saveItem(batchManager.uuid); - } - } - - batchManagerTemplateContent() { - const url = this.batchMgrUrl; - if (!url) { - throw Error('window._batch_manager_location must be set.'); - } - const packageInfo = { - name: 'Batch Manager', - identifier: this.batchManagerId - }; - const allContentType = dictToArray(ContentType); - const content = FillItemContent({ - name: packageInfo.name, - area: 'modal', - package_info: packageInfo, - permissions: [ - { - name: ComponentAction.StreamItems, - content_types: allContentType - } - ] - }); - if (isDesktopApplication()) { - content.local_url = this.batchMgrUrl; - } else { - content.hosted_url = this.batchMgrUrl; - } - return content; - } } diff --git a/app/assets/javascripts/ui_models/app_state/app_state.ts b/app/assets/javascripts/ui_models/app_state/app_state.ts index b17b82524..91330d101 100644 --- a/app/assets/javascripts/ui_models/app_state/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -146,7 +146,7 @@ export class AppState { this.noAccountWarning.reset(); } this.actionsMenu.reset(); - this.unsubApp(); + this.unsubApp?.(); this.unsubApp = undefined; this.observers.length = 0; this.appEventObserverRemovers.forEach((remover) => remover()); diff --git a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts index 80b5273f8..f263fcb7d 100644 --- a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts +++ b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts @@ -198,9 +198,15 @@ export class NoteTagsState { async removeTagFromActiveNote(tag: SNTag): Promise { const { activeNote } = this; if (activeNote) { - await this.application.changeItem(tag.uuid, (mutator) => { - mutator.removeItemAsRelationship(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) => { + mutator.removeItemAsRelationship(activeNote); + }); + }) + ); this.application.sync(); this.reloadTags(); } diff --git a/app/assets/javascripts/ui_models/app_state/notes_state.ts b/app/assets/javascripts/ui_models/app_state/notes_state.ts index 46f2ab65e..daa9881aa 100644 --- a/app/assets/javascripts/ui_models/app_state/notes_state.ts +++ b/app/assets/javascripts/ui_models/app_state/notes_state.ts @@ -346,13 +346,18 @@ export class NotesState { async removeTagFromSelectedNotes(tag: SNTag): Promise { const selectedNotes = Object.values(this.selectedNotes); - await this.application.changeItem(tag.uuid, (mutator) => { - for (const note of selectedNotes) { - mutator.removeItemAsRelationship(note); - } - }); + const descendantTags = this.application.getTagDescendants(tag); + const tagsToRemove = [...descendantTags, tag]; + await Promise.all( + tagsToRemove.map(async (tag) => { + await this.application.changeItem(tag.uuid, (mutator) => { + for (const note of selectedNotes) { + mutator.removeItemAsRelationship(note); + } + }); + }) + ); this.application.sync(); - } isTagInSelectedNotes(tag: SNTag): boolean { diff --git a/app/assets/javascripts/ui_models/application.ts b/app/assets/javascripts/ui_models/application.ts index 0803b531b..3577b4703 100644 --- a/app/assets/javascripts/ui_models/application.ts +++ b/app/assets/javascripts/ui_models/application.ts @@ -213,11 +213,6 @@ export class WebApplication extends SNApplication { async openModalComponent(component: SNComponent): Promise { switch (component.package_info?.identifier) { - case 'org.standardnotes.batch-manager': - if (!await this.authorizeBatchManagerAccess()) { - return; - } - break; case 'org.standardnotes.cloudlink': if (!await this.authorizeCloudLinkAccess()) { return; diff --git a/app/assets/javascripts/views/abstract/pure_view_ctrl.ts b/app/assets/javascripts/views/abstract/pure_view_ctrl.ts index 6f2a24297..71d7bae6f 100644 --- a/app/assets/javascripts/views/abstract/pure_view_ctrl.ts +++ b/app/assets/javascripts/views/abstract/pure_view_ctrl.ts @@ -40,8 +40,8 @@ export class PureViewCtrl

{ } deinit(): void { - this.unsubApp(); - this.unsubState(); + this.unsubApp?.(); + this.unsubState?.(); for (const disposer of this.reactionDisposers) { disposer(); } diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index cf4664b26..1d6acad2c 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -1,328 +1,9 @@ /* Components and utilities that are good candidates for extraction to StyleKit. */ -.border-2 { - border-width: 2px; -} - -.border-b-1 { - border-bottom-width: 1px; -} - -.border-background { - border-color: var(--sn-stylekit-background-color); -} - -.border-transparent { - border-color: transparent; -} - -.border-info { - border-color: var(--sn-stylekit-info-color); -} - -.border-neutral { - border-color: var(--sn-stylekit-neutral-color); -} - -.bg-border { - background-color: var(--sn-stylekit-border-color); -} - -.bg-clip-padding { - background-clip: padding-box; -} - -.bg-neutral { - background-color: var(--sn-stylekit-neutral-color); -} - -.focus-within\:border-background:focus-within { - border-color: var(--sn-stylekit-background-color); -} - -.focus\:border-bottom:focus { - border-bottom: 2px solid var(--sn-stylekit-info-color); -} - -.grid { - display: grid; -} - -.justify-start { - justify-content: flex-start; -} - -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.ml-1 { - margin-left: 0.25rem; -} - -.mr-1 { - margin-right: 0.25rem; -} - -.mr-10 { - margin-right: 2.5rem; -} - -.-mt-1 { - margin-top: -0.25rem; -} - -.-mr-1 { - margin-right: -0.25rem; -} - -.-mr-2 { - margin-right: -0.5rem; -} - -.py-1 { - padding-top: 0.25rem; - padding-bottom: 0.25rem; -} - -.pl-1 { - padding-left: 0.25rem; -} - -.pr-2 { - padding-right: 0.5rem; -} - -.px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; -} - -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -.py-1\.5 { - padding-top: 0.375rem; - padding-bottom: 0.375rem; -} - -.py-8 { - padding-top: 2rem; - padding-bottom: 2rem; -} - -.outline-none { - outline: none; -} - -.hidden { - display: none; -} - -.color-foreground { - color: var(--sn-stylekit-foreground-color); -} - -.color-danger { - color: var(--sn-stylekit-danger-color); -} - -.color-info { - color: var(--sn-stylekit-info-color); -} - -.ring-info { - box-shadow: 0 0 0 2px var(--sn-stylekit-info-color); -} - -.inner-ring-info { - box-shadow: inset 0 0 0 2px var(--sn-stylekit-info-color); -} - -.focus\:bg-contrast:focus { - @extend .bg-contrast; -} - -.hover\:color-text:hover { - @extend .color-text; -} - -.focus\:color-text:focus { - @extend .color-text; -} - -.hover\:bg-secondary-contrast:hover { - @extend .bg-secondary-contrast; -} - -.focus\:bg-secondary-contrast:focus { - @extend .bg-secondary-contrast; -} - -.focus\:inner-ring-info:focus { - @extend .inner-ring-info; -} - -.focus\:ring-info:focus { - @extend .ring-info; -} - -.focus-within\:ring-info:focus-within { - @extend .ring-info; -} - -.text-left { - text-align: left; -} - -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; -} - -.w-0 { - width: 0; -} - -.w-3\.5 { - width: 0.875rem; -} - -.w-5 { - width: 1.25rem; -} - -.w-8 { - width: 2rem; -} - -.max-w-290px { - max-width: 290px; -} - -.max-w-xs { - max-width: 20rem; -} - -.max-w-40 { - max-width: 10rem; -} - -.min-w-5 { - min-width: 1.25rem; -} - -.min-w-40 { - min-width: 10rem; -} - -.h-1px { - height: 1px; -} - -.h-0 { - height: 0; -} - -.h-3\.5 { - height: 0.875rem; -} - -.h-4\.5 { - height: 1.125rem; -} - -.h-5 { - height: 1.25rem; -} - -.h-6 { - height: 1.5rem; -} - -.h-7 { - height: 1.75rem; -} - -.h-8 { - height: 2rem; -} - -.h-9 { - height: 2.25rem; -} - -.h-10 { - height: 2.5rem; -} - -.h-18 { - height: 4.5rem; -} - .h-90vh { height: 90vh; } -.max-h-120 { - max-height: 30rem; -} - -.min-h-5 { - min-height: 1.25rem; -} - -.fixed { - position: fixed; -} - -.overflow-y-auto { - overflow-y: auto; -} - -.overflow-auto { - overflow: auto; -} - -.overflow-hidden { - overflow: hidden; -} - -.overflow-ellipsis { - text-overflow: ellipsis; -} - -.items-start { - align-items: flex-start; -} - -.p-2 { - padding: 0.5rem; -} - -.flex-wrap { - flex-wrap: wrap; -} - -.whitespace-pre-wrap { - white-space: pre-wrap; -} - -.whitespace-nowrap { - white-space: nowrap; -} - -.w-80 { - width: 20rem; -} - -.w-70 { - width: 17.5rem; -} - /** * A button that is just an icon. Separated from .sn-button because there * is almost no style overlap. @@ -376,7 +57,7 @@ &[data-state='collapsed'] { display: none; } - + &.sn-dropdown--animated { @extend .transition-transform; @extend .duration-150; diff --git a/index.html b/index.html index c91af2be3..1c0798bc1 100644 --- a/index.html +++ b/index.html @@ -32,14 +32,12 @@ data-default-sync-server="<%= env.DEV_DEFAULT_SYNC_SERVER %>" data-next-version-sync-server="<%= env.DEV_NEXT_VERSION_SYNC_SERVER %>" data-extensions-manager-location="<%= env.DEV_EXTENSIONS_MANAGER_LOCATION %>" - data-batch-manager-location="<%= env.DEV_BATCH_MANAGER_LOCATION %>" data-bugsnag-api-key="<%= env.DEV_BUGSNAG_API_KEY %>" > diff --git a/package.json b/package.json index 71f7c0de1..d76cefddc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "standard-notes-web", - "version": "3.8.1", + "version": "3.8.2", "license": "AGPL-3.0-or-later", "repository": { "type": "git", @@ -55,7 +55,7 @@ "pug-loader": "^2.4.0", "sass-loader": "^8.0.2", "serve-static": "^1.14.1", - "sn-stylekit": "5.1.0", + "sn-stylekit": "5.2.1", "ts-loader": "^8.0.17", "typescript": "4.2.3", "typescript-eslint": "0.0.1-alpha.0", @@ -71,7 +71,7 @@ "@reach/checkbox": "^0.13.2", "@reach/dialog": "^0.13.0", "@standardnotes/sncrypto-web": "1.2.10", - "@standardnotes/snjs": "2.6.2", + "@standardnotes/snjs": "2.7.1", "mobx": "^6.1.6", "mobx-react-lite": "^3.2.0", "preact": "^10.5.12" diff --git a/public/extensions/batch-manager b/public/extensions/batch-manager deleted file mode 160000 index 2d1ba6ac9..000000000 --- a/public/extensions/batch-manager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2d1ba6ac9f09193e40c4241a70605ecb3c15d3c7 diff --git a/yarn.lock b/yarn.lock index 42e488c2d..ab9a5793e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1936,10 +1936,10 @@ "@standardnotes/sncrypto-common" "^1.2.7" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.6.2.tgz#dbd835f8c0fcdf951f636b3a5b6d0b54c00de458" - integrity sha512-/6U9sEBtT2MouwbH0OBaQW4eqnvwwNnXUXq+zDfV8UKqJPoEnwLGumnb72cJ8d/67e0haoltc2C8wHicbZgFrQ== +"@standardnotes/snjs@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.7.1.tgz#e8073942fe9a8f762989ab75df94407b9934ea3e" + integrity sha512-kCZU4Tx6vNYcEeh22ax8Vfr/LYEbgAqI9nSC6jB8MTg6nJzCuh4b/nngW+yVUz91phu156XN0YeMyj7HJ3DWVw== dependencies: "@standardnotes/auth" "^2.0.0" "@standardnotes/sncrypto-common" "^1.2.9" @@ -7815,10 +7815,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -sn-stylekit@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.1.0.tgz#97ce7323834ff7f3645ed4463beb3ad4e42f7e7e" - integrity sha512-SjKJYGRnR1iCVtllqJyW9/gWV7V56MJrkXHEo5+C9Ch3syRRlG3k+AwC0vC8ms6PWxpHJUdCHLQROvFOBPQuCw== +sn-stylekit@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.2.1.tgz#d56a38017d6b45f5c2ebcf7b2c2db94c50a26e27" + integrity sha512-kupuH9XlPy5TuO2a2E+sLQnF41VQ0a4cwHRltNhQSI6O135n4HkkDYgyEAvy5DKcc0aun4KHy5mz9kYefQvciw== snapdragon-node@^2.0.1: version "2.1.1"