From 29b72c71cf9c21c00ce71da7a9a2889fdcc573a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Thu, 14 Oct 2021 11:37:44 +0200 Subject: [PATCH 01/53] fix: update snjs to 2.14.14 (#684) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 422157e8f..592beac99 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@reach/listbox": "^0.16.1", "@standardnotes/features": "1.6.1", "@standardnotes/sncrypto-web": "1.5.2", - "@standardnotes/snjs": "2.14.13", + "@standardnotes/snjs": "2.14.14", "mobx": "^6.3.2", "mobx-react-lite": "^3.2.0", "preact": "^10.5.12", diff --git a/yarn.lock b/yarn.lock index 9e833e9e9..a78a7e302 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2149,10 +2149,10 @@ "@standardnotes/sncrypto-common" "^1.5.2" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@2.14.13": - version "2.14.13" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.14.13.tgz#791935559538bf51484159d87651b667dc2dcebd" - integrity sha512-nLmP8BvN8zqc7i6mspr2UKhGxdHFZhW0+uUbGu3yjLiMAnfQpxx8pLP9uWdshx+5Cc8cCzCBq+ekA0OZt4UK3Q== +"@standardnotes/snjs@2.14.14": + version "2.14.14" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.14.14.tgz#02886f431570a19a7dc5de0411fb19bb864281f2" + integrity sha512-IQVsRLFhbmRtF2kB9mXnccjY2lBCb+k1biLmM6lF5ZpanxPPeW/Z5H398QWgCFzfKu70nocSXO+SqmLswKxnLQ== dependencies: "@standardnotes/auth" "3.7.2" "@standardnotes/common" "1.2.0" From 3953713f4533eaa5401d568c94dbc46188232a53 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 15 Oct 2021 19:46:08 +0530 Subject: [PATCH 02/53] feat: Add app version & server to account menu (#682) * feat: Add app version & server to account menu * fix: Fix email bottom margin * fix: Fix spacing of email & server in account menu * feat: Move version to right of "Help & Feedback" button --- .../AccountMenu/GeneralAccountMenu.tsx | 17 +++++++++++------ app/assets/stylesheets/_sn.scss | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx index 81fd0f5ea..1716c6319 100644 --- a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx +++ b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx @@ -8,6 +8,7 @@ import { STRING_GENERIC_SYNC_ERROR } from '@/strings'; import { useState } from 'preact/hooks'; import { AccountMenuPane } from '.'; import { FunctionComponent } from 'preact'; +import { AppVersion } from '@/version'; type Props = { appState: AppState; @@ -54,7 +55,7 @@ export const GeneralAccountMenu: FunctionComponent = observer( return ( <> -
+
Account
@@ -62,9 +63,10 @@ export const GeneralAccountMenu: FunctionComponent = observer(
{user ? ( <> -
+
You're signed in as:
-
{user.email}
+
{user.email}
+ {application.getHost()}
{isSyncingInProgress ? ( @@ -136,15 +138,18 @@ export const GeneralAccountMenu: FunctionComponent = observer( )} {user ? ( <> diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 6e724a42d..8f9f2cf73 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -219,6 +219,11 @@ margin-right: 0.75rem; } +.my-0\.5 { + margin-top: 0.125rem; + margin-bottom: 0.125rem; +} + .my-1\.5 { margin-top: 0.375rem; margin-bottom: 0.375rem; From 474f1e60e3bf313aad47b8cd2ef4b41494f5e06f Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sun, 17 Oct 2021 02:16:44 +0530 Subject: [PATCH 03/53] fix: "Tools" preferences' toggles not showing correct initial state (#689) * fix: Tools toggles not showing correct initial state * Update app/assets/javascripts/preferences/panes/general-segments/Tools.tsx Co-authored-by: Mough --- .../preferences/panes/general-segments/Tools.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/preferences/panes/general-segments/Tools.tsx b/app/assets/javascripts/preferences/panes/general-segments/Tools.tsx index 3cd46b114..380a93b7b 100644 --- a/app/assets/javascripts/preferences/panes/general-segments/Tools.tsx +++ b/app/assets/javascripts/preferences/panes/general-segments/Tools.tsx @@ -20,13 +20,13 @@ type Props = { export const Tools: FunctionalComponent = observer( ({ application }: Props) => { const [monospaceFont, setMonospaceFont] = useState(() => - application.getPreference(PrefKey.EditorMonospaceEnabled) + application.getPreference(PrefKey.EditorMonospaceEnabled, true) ); const [marginResizers, setMarginResizers] = useState(() => - application.getPreference(PrefKey.EditorResizersEnabled) + application.getPreference(PrefKey.EditorResizersEnabled, true) ); const [spellcheck, setSpellcheck] = useState(() => - application.getPreference(PrefKey.EditorSpellcheck) + application.getPreference(PrefKey.EditorSpellcheck, true) ); const toggleMonospaceFont = () => { From 4ce9264572e00f85c3739a525fa9cfc0604a79f8 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sun, 17 Oct 2021 18:40:41 +0530 Subject: [PATCH 04/53] fix: Reload textarea on spellcheck pref change (#690) --- app/assets/javascripts/views/editor/editor_view.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index 9d6c282d8..b653a739f 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -671,6 +671,13 @@ class EditorViewCtrl extends PureViewCtrl { PrefKey.EditorResizersEnabled, true ); + + if (spellcheck !== this.state.spellcheck) { + await this.setState({ textareaUnloading: true }); + await this.setState({ textareaUnloading: false }); + this.reloadFont(); + } + await this.setState({ monospaceFont, spellcheck, From bbeab4f6232632c5205d1c939fc3819eeacba8c5 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sun, 17 Oct 2021 21:24:02 +0530 Subject: [PATCH 05/53] feat: Remove obselete code related to editor preferences (#691) --- .../javascripts/views/editor/editor_view.ts | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index b653a739f..8f5a4eee2 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -108,10 +108,6 @@ class EditorViewCtrl extends PureViewCtrl { private removeComponentsObserver!: () => void; - prefKeyMonospace: string; - prefKeySpellcheck: string; - prefKeyMarginResizers: string; - /* @ngInject */ constructor($timeout: ng.ITimeoutService) { super($timeout); @@ -121,10 +117,6 @@ class EditorViewCtrl extends PureViewCtrl { this.rightPanelPuppet = { onReady: () => this.reloadPreferences(), }; - /** Used by .pug template */ - this.prefKeyMonospace = PrefKey.EditorMonospaceEnabled; - this.prefKeySpellcheck = PrefKey.EditorSpellcheck; - this.prefKeyMarginResizers = PrefKey.EditorResizersEnabled; this.editorMenuOnSelect = this.editorMenuOnSelect.bind(this); this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this); @@ -722,30 +714,6 @@ class EditorViewCtrl extends PureViewCtrl { } } - async toggleWebPrefKey(key: PrefKey) { - const currentValue = (this.state as any)[key]; - await this.application.setPreference(key, !currentValue); - await this.setState({ - [key]: !currentValue, - }); - this.reloadFont(); - - if (key === PrefKey.EditorSpellcheck) { - /** Allows textarea to reload */ - await this.setState({ textareaUnloading: true }); - await this.setState({ textareaUnloading: false }); - this.reloadFont(); - } else if ( - key === PrefKey.EditorResizersEnabled && - this.state[key] === true - ) { - this.$timeout(() => { - this.leftPanelPuppet!.flash!(); - this.rightPanelPuppet!.flash!(); - }); - } - } - /** @components */ registerComponentHandler() { From c8dc07d42b706ca6dd694f0de95cf676b53ca64c Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 19 Oct 2021 09:58:46 +0530 Subject: [PATCH 06/53] feat: Add quick settings menu with theme switcher and other changes (#673) Co-authored-by: Mo Bitar Co-authored-by: Antonella Sgarlatta --- app/assets/icons/ic-themes.svg | 2 +- app/assets/javascripts/app.ts | 2 + .../AccountMenu/AdvancedOptions.tsx | 2 +- .../AccountMenu/ConfirmPassword.tsx | 6 +- .../AccountMenu/GeneralAccountMenu.tsx | 67 +++- .../components/AccountMenu/index.tsx | 20 +- .../components/QuickSettingsMenu.tsx | 315 ++++++++++++++++++ .../ui_models/app_state/account_menu_state.ts | 15 +- .../ui_models/app_state/app_state.ts | 4 +- .../app_state/quick_settings_state.ts | 42 +++ .../javascripts/views/footer/footer-view.pug | 10 +- .../javascripts/views/footer/footer_view.ts | 34 +- app/assets/stylesheets/_menus.scss | 9 +- app/assets/stylesheets/_sn.scss | 38 +++ 14 files changed, 529 insertions(+), 37 deletions(-) create mode 100644 app/assets/javascripts/components/QuickSettingsMenu.tsx create mode 100644 app/assets/javascripts/ui_models/app_state/quick_settings_state.ts diff --git a/app/assets/icons/ic-themes.svg b/app/assets/icons/ic-themes.svg index 33abb061a..62ac3133b 100644 --- a/app/assets/icons/ic-themes.svg +++ b/app/assets/icons/ic-themes.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 971f793eb..e94e181c9 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -64,6 +64,7 @@ import { IconDirective } from './components/Icon'; import { NoteTagsContainerDirective } from './components/NoteTagsContainer'; import { PreferencesDirective } from './preferences'; import { AppVersion, IsWebPlatform } from '@/version'; +import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu'; function reloadHiddenFirefoxTab(): boolean { /** @@ -154,6 +155,7 @@ const startApplication: StartApplication = async function startApplication( .directive('syncResolutionMenu', () => new SyncResolutionMenu()) .directive('sessionsModal', SessionsModalDirective) .directive('accountMenu', AccountMenuDirective) + .directive('quickSettingsMenu', QuickSettingsMenuDirective) .directive('noAccountWarning', NoAccountWarningDirective) .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective) .directive('searchOptions', SearchOptionsDirective) diff --git a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx index 82865ede9..1a75878df 100644 --- a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx +++ b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx @@ -39,7 +39,7 @@ export const AdvancedOptions: FunctionComponent = observer( return ( <> ) : null} - +
); } ); diff --git a/app/assets/javascripts/components/AccountMenu/index.tsx b/app/assets/javascripts/components/AccountMenu/index.tsx index 77da5608c..54de2a9ab 100644 --- a/app/assets/javascripts/components/AccountMenu/index.tsx +++ b/app/assets/javascripts/components/AccountMenu/index.tsx @@ -9,6 +9,7 @@ import { SignInPane } from './SignIn'; import { CreateAccount } from './CreateAccount'; import { ConfirmSignoutContainer } from '../ConfirmSignoutModal'; import { ConfirmPassword } from './ConfirmPassword'; +import { JSXInternal } from 'preact/src/jsx'; export enum AccountMenuPane { GeneralMenu, @@ -87,14 +88,31 @@ const AccountMenu: FunctionComponent = observer( closeAccountMenu, } = appState.accountMenu; + const handleKeyDown: JSXInternal.KeyboardEventHandler = ( + event + ) => { + switch (event.key) { + case 'Escape': + if (currentPane === AccountMenuPane.GeneralMenu) { + closeAccountMenu(); + } else if (currentPane === AccountMenuPane.ConfirmPassword) { + setCurrentPane(AccountMenuPane.Register); + } else { + setCurrentPane(AccountMenuPane.GeneralMenu); + } + break; + } + }; + return (
void; +}; + +type MenuProps = { + appState: AppState; + application: WebApplication; +}; + +const ThemeButton: FunctionComponent = ({ + application, + theme, + onBlur, +}) => { + const toggleTheme = () => { + if (theme.isLayerable() || !theme.active) { + application.toggleComponent(theme); + } + }; + + return ( + + ); +}; + +const QuickSettingsMenu: FunctionComponent = observer( + ({ application, appState }) => { + const { closeQuickSettingsMenu, shouldAnimateCloseMenu } = + appState.quickSettingsMenu; + const [themes, setThemes] = useState([]); + const [themesMenuOpen, setThemesMenuOpen] = useState(false); + const [themesMenuPosition, setThemesMenuPosition] = useState({}); + const [defaultThemeOn, setDefaultThemeOn] = useState(false); + + const themesMenuRef = useRef(); + const themesButtonRef = useRef(); + const prefsButtonRef = useRef(); + const quickSettingsMenuRef = useRef(); + const defaultThemeButtonRef = useRef(); + + const reloadThemes = useCallback(() => { + application.streamItems(ContentType.Theme, () => { + const themes = application.getDisplayableItems( + ContentType.Theme + ) as SNTheme[]; + setThemes( + themes.sort((a, b) => { + const aIsLayerable = a.isLayerable(); + const bIsLayerable = b.isLayerable(); + + if (aIsLayerable && !bIsLayerable) { + return 1; + } else if (!aIsLayerable && bIsLayerable) { + return -1; + } else { + return a.package_info.name.toLowerCase() < + b.package_info.name.toLowerCase() + ? -1 + : 1; + } + }) + ); + setDefaultThemeOn( + !themes.find((theme) => theme.active && !theme.isLayerable()) + ); + }); + }, [application]); + + useEffect(() => { + reloadThemes(); + }, [reloadThemes]); + + useEffect(() => { + if (themesMenuOpen) { + defaultThemeButtonRef.current.focus(); + } + }, [themesMenuOpen]); + + useEffect(() => { + prefsButtonRef.current.focus(); + }, []); + + const [closeOnBlur] = useCloseOnBlur(themesMenuRef, setThemesMenuOpen); + + const toggleThemesMenu = () => { + if (!themesMenuOpen) { + const themesButtonRect = + themesButtonRef.current.getBoundingClientRect(); + setThemesMenuPosition({ + left: themesButtonRect.right, + bottom: + document.documentElement.clientHeight - themesButtonRect.bottom, + }); + setThemesMenuOpen(true); + } else { + setThemesMenuOpen(false); + } + }; + + const openPreferences = () => { + closeQuickSettingsMenu(); + appState.preferences.openPreferences(); + }; + + const handleBtnKeyDown: React.KeyboardEventHandler = ( + event + ) => { + switch (event.key) { + case 'Escape': + setThemesMenuOpen(false); + themesButtonRef.current.focus(); + break; + case 'ArrowRight': + if (!themesMenuOpen) { + toggleThemesMenu(); + } + } + }; + + const handleQuickSettingsKeyDown: JSXInternal.KeyboardEventHandler = + (event) => { + const items: NodeListOf = + quickSettingsMenuRef.current.querySelectorAll(':scope > button'); + const currentFocusedIndex = Array.from(items).findIndex( + (btn) => btn === document.activeElement + ); + + if (!themesMenuOpen) { + switch (event.key) { + case 'Escape': + closeQuickSettingsMenu(); + break; + case 'ArrowDown': + if (items[currentFocusedIndex + 1]) { + items[currentFocusedIndex + 1].focus(); + } else { + items[0].focus(); + } + break; + case 'ArrowUp': + if (items[currentFocusedIndex - 1]) { + items[currentFocusedIndex - 1].focus(); + } else { + items[items.length - 1].focus(); + } + break; + } + } + }; + + const handlePanelKeyDown: React.KeyboardEventHandler = ( + event + ) => { + const themes = themesMenuRef.current.querySelectorAll('button'); + const currentFocusedIndex = Array.from(themes).findIndex( + (themeBtn) => themeBtn === document.activeElement + ); + + switch (event.key) { + case 'Escape': + case 'ArrowLeft': + event.stopPropagation(); + setThemesMenuOpen(false); + themesButtonRef.current.focus(); + break; + case 'ArrowDown': + if (themes[currentFocusedIndex + 1]) { + themes[currentFocusedIndex + 1].focus(); + } else { + themes[0].focus(); + } + break; + case 'ArrowUp': + if (themes[currentFocusedIndex - 1]) { + themes[currentFocusedIndex - 1].focus(); + } else { + themes[themes.length - 1].focus(); + } + break; + } + }; + + const toggleDefaultTheme = () => { + const activeTheme = themes.find( + (theme) => theme.active && !theme.isLayerable() + ); + if (activeTheme) application.toggleComponent(activeTheme); + }; + + return ( +
+
+
+ Quick Settings +
+ + +
+ + Themes +
+ +
+ +
+ Themes +
+ + {themes.map((theme) => ( + + ))} +
+
+
+ +
+
+ ); + } +); + +export const QuickSettingsMenuDirective = + toDirective(QuickSettingsMenu); diff --git a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts index 7a8dbafdd..8fe287989 100644 --- a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts +++ b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts @@ -52,6 +52,7 @@ export class AccountMenuState { shouldAnimateCloseMenu: observable, setShow: action, + setShouldAnimateClose: action, toggleShow: action, setSigningOut: action, setIsEncryptionEnabled: action, @@ -95,11 +96,15 @@ export class AccountMenuState { this.show = show; }; + setShouldAnimateClose = (shouldAnimateCloseMenu: boolean): void => { + this.shouldAnimateCloseMenu = shouldAnimateCloseMenu; + }; + closeAccountMenu = (): void => { - this.shouldAnimateCloseMenu = true; + this.setShouldAnimateClose(true); setTimeout(() => { this.setShow(false); - this.shouldAnimateCloseMenu = false; + this.setShouldAnimateClose(false); this.setCurrentPane(AccountMenuPane.GeneralMenu); }, 150); }; @@ -137,7 +142,11 @@ export class AccountMenuState { }; toggleShow = (): void => { - this.show = !this.show; + if (this.show) { + this.closeAccountMenu(); + } else { + this.setShow(true); + } }; setOtherSessionsSignOut = (otherSessionsSignOut: boolean): void => { 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 272c3fb3a..ad3b87ae3 100644 --- a/app/assets/javascripts/ui_models/app_state/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -23,6 +23,7 @@ import { NotesState } from './notes_state'; import { TagsState } from './tags_state'; import { AccountMenuState } from '@/ui_models/app_state/account_menu_state'; import { PreferencesState } from './preferences_state'; +import { QuickSettingsState } from './quick_settings_state'; export enum AppStateEvent { TagChanged, @@ -62,6 +63,7 @@ export class AppState { onVisibilityChange: any; selectedTag?: SNTag; showBetaWarning: boolean; + readonly quickSettingsMenu = new QuickSettingsState(); readonly accountMenu: AccountMenuState; readonly actionsMenu = new ActionsMenuState(); readonly preferences = new PreferencesState(); @@ -105,7 +107,7 @@ export class AppState { ); this.accountMenu = new AccountMenuState( application, - this.appEventObserverRemovers, + this.appEventObserverRemovers ); this.searchOptions = new SearchOptionsState( application, diff --git a/app/assets/javascripts/ui_models/app_state/quick_settings_state.ts b/app/assets/javascripts/ui_models/app_state/quick_settings_state.ts new file mode 100644 index 000000000..f3571cb00 --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/quick_settings_state.ts @@ -0,0 +1,42 @@ +import { action, makeObservable, observable } from 'mobx'; + +export class QuickSettingsState { + open = false; + shouldAnimateCloseMenu = false; + + constructor() { + makeObservable(this, { + open: observable, + shouldAnimateCloseMenu: observable, + + setOpen: action, + setShouldAnimateCloseMenu: action, + toggle: action, + closeQuickSettingsMenu: action, + }); + } + + setOpen = (open: boolean): void => { + this.open = open; + }; + + setShouldAnimateCloseMenu = (shouldAnimate: boolean): void => { + this.shouldAnimateCloseMenu = shouldAnimate; + }; + + toggle = (): void => { + if (this.open) { + this.closeQuickSettingsMenu(); + } else { + this.setOpen(true); + } + }; + + closeQuickSettingsMenu = (): void => { + this.setShouldAnimateCloseMenu(true); + setTimeout(() => { + this.setOpen(false); + this.setShouldAnimateCloseMenu(false); + }, 150); + }; +} diff --git a/app/assets/javascripts/views/footer/footer-view.pug b/app/assets/javascripts/views/footer/footer-view.pug index 5b0f57b9d..e24019f37 100644 --- a/app/assets/javascripts/views/footer/footer-view.pug +++ b/app/assets/javascripts/views/footer/footer-view.pug @@ -23,14 +23,22 @@ ng-if='ctrl.showAccountMenu', ) .sk-app-bar-item.ml-0-important( - ng-click='ctrl.clickPreferences()' + click-outside='ctrl.clickOutsideQuickSettingsMenu()', + is-open='ctrl.showQuickSettingsMenu', + ng-click='ctrl.quickSettingsPressed()' ) .w-8.h-full.flex.items-center.justify-center.cursor-pointer .h-5 icon( type="tune" class-name="rounded hover:color-info" + ng-class="{'color-info': ctrl.showQuickSettingsMenu}" ) + quick-settings-menu( + ng-click='$event.stopPropagation()', + app-state='ctrl.appState' + application='ctrl.application' + ng-if='ctrl.showQuickSettingsMenu',) .sk-app-bar-item a.no-decoration.sk-label.title( href='https://standardnotes.com/help', diff --git a/app/assets/javascripts/views/footer/footer_view.ts b/app/assets/javascripts/views/footer/footer_view.ts index 65ff30427..c0064484c 100644 --- a/app/assets/javascripts/views/footer/footer_view.ts +++ b/app/assets/javascripts/views/footer/footer_view.ts @@ -64,6 +64,7 @@ class FooterViewCtrl extends PureViewCtrl< public user?: any; private offline = true; public showAccountMenu = false; + public showQuickSettingsMenu = false; private didCheckForOffline = false; private queueExtReload = false; private reloadInProgress = false; @@ -115,6 +116,7 @@ class FooterViewCtrl extends PureViewCtrl< this.autorun(() => { const showBetaWarning = this.appState.showBetaWarning; this.showAccountMenu = this.appState.accountMenu.show; + this.showQuickSettingsMenu = this.appState.quickSettingsMenu.open; this.setState({ showBetaWarning: showBetaWarning, showDataUpgrade: !showBetaWarning, @@ -449,10 +451,21 @@ class FooterViewCtrl extends PureViewCtrl< } accountMenuPressed() { + this.appState.quickSettingsMenu.closeQuickSettingsMenu(); this.appState.accountMenu.toggleShow(); this.closeAllRooms(); } + quickSettingsPressed() { + this.appState.accountMenu.closeAccountMenu(); + if (this.themesWithIcons.length > 0) { + this.appState.quickSettingsMenu.toggle(); + } else { + this.appState.preferences.openPreferences(); + } + this.closeAllRooms(); + } + toggleSyncResolutionMenu() { this.showSyncResolution = !this.showSyncResolution; } @@ -476,22 +489,7 @@ class FooterViewCtrl extends PureViewCtrl< } reloadDockShortcuts() { - const shortcuts = []; - for (const theme of this.themesWithIcons) { - if (!theme.package_info) { - continue; - } - const name = theme.package_info.name; - const icon = theme.package_info.dock_icon; - if (!icon) { - continue; - } - shortcuts.push({ - name: name, - component: theme, - icon: icon, - } as DockShortcut); - } + const shortcuts: DockShortcut[] = []; this.setState({ dockShortcuts: shortcuts.sort((a, b) => { /** Circles first, then images */ @@ -556,8 +554,8 @@ class FooterViewCtrl extends PureViewCtrl< this.appState.accountMenu.closeAccountMenu(); } - clickPreferences() { - this.appState.preferences.openPreferences(); + clickOutsideQuickSettingsMenu() { + this.appState.quickSettingsMenu.closeQuickSettingsMenu(); } } diff --git a/app/assets/stylesheets/_menus.scss b/app/assets/stylesheets/_menus.scss index d65ac5741..b79039dbd 100644 --- a/app/assets/stylesheets/_menus.scss +++ b/app/assets/stylesheets/_menus.scss @@ -17,13 +17,20 @@ max-height: calc(85vh - 90px); } -.sn-account-menu { +.sn-account-menu, +.sn-quick-settings-menu { z-index: $z-index-footer-bar-item-panel; @extend .bottom-100; @extend .left-0; @extend .cursor-auto; } +.sn-menu-border { + @extend .border-1; + @extend .border-solid; + @extend .border-gray-300; +} + .sn-account-menu-headline { @extend .sk-h2; @extend .sk-bold; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 8f9f2cf73..4638703f0 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -384,3 +384,41 @@ .cursor-auto { cursor: auto; } + +.top-1\/2 { + top: 50%; +} + +.left-1\/2 { + left: 50%; +} + +.-translate-1\/2 { + transform: translate(-50%, -50%); +} + +.pseudo-radio-btn { + @extend .w-4; + @extend .h-4; + @extend .border-2; + @extend .border-solid; + @extend .border-info; + @extend .rounded-full; + @extend .relative; +} + +.pseudo-radio-btn--checked::after { + content: ''; + @extend .bg-info; + @extend .absolute; + @extend .top-1\/2; + @extend .left-1\/2; + @extend .-translate-1\/2; + @extend .w-2; + @extend .h-2; + @extend .rounded-full; +} + +.focus\:bg-info-backdrop:focus { + background-color: var(--sn-stylekit-info-backdrop-color); +} From 29138ea597841dc321294b308d602cecd61170ab Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 19 Oct 2021 19:06:57 +0530 Subject: [PATCH 07/53] fix: Fix spacing in "Help & feedback" button in general account menu (#693) --- .../javascripts/components/AccountMenu/GeneralAccountMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx index 5650d199f..c1ed8da97 100644 --- a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx +++ b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx @@ -185,7 +185,7 @@ export const GeneralAccountMenu: FunctionComponent = observer( )} ); +const countNoteAttributes = (text: string) => { + try { + JSON.parse(text); + return { + characters: 'N/A', + words: 'N/A', + paragraphs: 'N/A', + }; + } catch { + const characters = text.length; + const words = text.match(/[\w’'-]+\b/g)?.length; + const paragraphs = text.replace(/\n$/gm, '').split(/\n/).length; + + return { + characters, + words, + paragraphs, + }; + } +}; + +const calculateReadTime = (words: number) => { + const timeToRead = Math.round(words / 200); + if (timeToRead === 0) { + return '< 1 minute'; + } else { + return `${timeToRead} ${timeToRead > 1 ? 'minutes' : 'minute'}`; + } +}; + +const formatDate = (date: Date | undefined) => { + if (!date) return; + return `${date.toDateString()} ${date.toLocaleTimeString()}`; +}; + +const NoteAttributes: FunctionComponent<{ note: SNNote }> = ({ note }) => { + const { words, characters, paragraphs } = useMemo( + () => countNoteAttributes(note.text), + [note.text] + ); + + const readTime = useMemo( + () => (typeof words === 'number' ? calculateReadTime(words) : 'N/A'), + [words] + ); + + const dateLastModified = useMemo( + () => formatDate(note.serverUpdatedAt), + [note.serverUpdatedAt] + ); + + const dateCreated = useMemo( + () => formatDate(note.created_at), + [note.created_at] + ); + + return ( +
+ {typeof words === 'number' ? ( + <> +
+ {words} words · {characters} characters · {paragraphs} paragraphs +
+
+ Read time: {readTime} +
+ + ) : null} +
+ Last modified: {dateLastModified} +
+
+ Created: {dateCreated} +
+
+ Note ID: {note.uuid} +
+
+ ); +}; + export const NotesOptions = observer( ({ application, appState, closeOnBlur, onSubmenuChange }: Props) => { const [tagsMenuOpen, setTagsMenuOpen] = useState(false); @@ -45,8 +127,9 @@ export const NotesOptions = observer( top: 0, right: 0, }); - const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = - useState('auto'); + const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = useState( + 'auto' + ); const [altKeyDown, setAltKeyDown] = useState(false); const toggleOn = (condition: (note: SNNote) => boolean) => { @@ -86,7 +169,7 @@ export const NotesOptions = observer( }, onKeyUp: () => { setAltKeyDown(false); - } + }, }); return () => { @@ -163,7 +246,7 @@ export const NotesOptions = observer( Protect -
+
{appState.tags.tagsCount > 0 && ( )} + {notes.length === 1 ? ( + <> +
+ + + ) : null} ); } diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 4638703f0..dd594c2f1 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -274,6 +274,10 @@ min-width: 3.75rem; } +.min-h-1px { + min-height: 1px; +} + .min-h-1 { min-height: 0.25rem; } From f9b15262c7c6efb9aff8da44e82f39c28f0bf23d Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 19 Oct 2021 20:20:42 +0530 Subject: [PATCH 09/53] feat: Purchase "Create account" & "Sign in" flows and Floating label input (#672) --- app/assets/javascripts/app.ts | 4 +- .../components/FloatingLabelInput.tsx | 75 ++++++ .../purchaseFlow/PurchaseFlowView.tsx | 68 +++++ .../purchaseFlow/PurchaseFlowWrapper.tsx | 33 +++ app/assets/javascripts/purchaseFlow/index.ts | 8 + .../purchaseFlow/panes/CreateAccount.tsx | 200 ++++++++++++++ .../javascripts/purchaseFlow/panes/SignIn.tsx | 175 +++++++++++++ .../ui_models/app_state/app_state.ts | 5 + .../app_state/purchase_flow_state.ts | 38 +++ .../views/application/application-view.pug | 5 +- app/assets/stylesheets/_main.scss | 6 + app/assets/stylesheets/_sn.scss | 189 ++++++++++++++ app/assets/svg/blue-dot.svg | 3 + app/assets/svg/circle-55.svg | 11 + .../svg/create-account-illustration.svg | 245 ++++++++++++++++++ .../svg/diamond-with-horizontal-lines.svg | 12 + app/assets/svg/ic-sn-logo-full.svg | 4 + 17 files changed, 1079 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/components/FloatingLabelInput.tsx create mode 100644 app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx create mode 100644 app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx create mode 100644 app/assets/javascripts/purchaseFlow/index.ts create mode 100644 app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx create mode 100644 app/assets/javascripts/purchaseFlow/panes/SignIn.tsx create mode 100644 app/assets/javascripts/ui_models/app_state/purchase_flow_state.ts create mode 100644 app/assets/svg/blue-dot.svg create mode 100644 app/assets/svg/circle-55.svg create mode 100644 app/assets/svg/create-account-illustration.svg create mode 100644 app/assets/svg/diamond-with-horizontal-lines.svg create mode 100644 app/assets/svg/ic-sn-logo-full.svg diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index e94e181c9..09ae253cb 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -64,6 +64,7 @@ import { IconDirective } from './components/Icon'; import { NoteTagsContainerDirective } from './components/NoteTagsContainer'; import { PreferencesDirective } from './preferences'; import { AppVersion, IsWebPlatform } from '@/version'; +import { PurchaseFlowDirective } from './purchaseFlow'; import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu'; function reloadHiddenFirefoxTab(): boolean { @@ -165,7 +166,8 @@ const startApplication: StartApplication = async function startApplication( .directive('notesOptionsPanel', NotesOptionsPanelDirective) .directive('icon', IconDirective) .directive('noteTagsContainer', NoteTagsContainerDirective) - .directive('preferences', PreferencesDirective); + .directive('preferences', PreferencesDirective) + .directive('purchaseFlow', PurchaseFlowDirective); // Filters angular.module('app').filter('trusted', ['$sce', trusted]); diff --git a/app/assets/javascripts/components/FloatingLabelInput.tsx b/app/assets/javascripts/components/FloatingLabelInput.tsx new file mode 100644 index 000000000..79d8820b4 --- /dev/null +++ b/app/assets/javascripts/components/FloatingLabelInput.tsx @@ -0,0 +1,75 @@ +import { FunctionComponent, Ref } from 'preact'; +import { JSXInternal } from 'preact/src/jsx'; +import { forwardRef } from 'preact/compat'; +import { useState } from 'preact/hooks'; + +type Props = { + id: string; + type: 'text' | 'email' | 'password'; // Have no use cases for other types so far + label: string; + value: string; + onChange: JSXInternal.GenericEventHandler; + disabled?: boolean; + className?: string; + labelClassName?: string; + inputClassName?: string; + isInvalid?: boolean; +}; + +export const FloatingLabelInput: FunctionComponent = forwardRef( + ( + { + id, + type, + label, + disabled, + value, + isInvalid, + onChange, + className = '', + labelClassName = '', + inputClassName = '', + }, + ref: Ref + ) => { + const [focused, setFocused] = useState(false); + + const BASE_CLASSNAME = `relative bg-default`; + + const LABEL_CLASSNAME = `hidden absolute ${ + !focused ? 'color-neutral' : 'color-info' + } ${focused || value ? 'flex top-0 left-2 pt-1.5 px-1' : ''} ${ + isInvalid ? 'color-dark-red' : '' + } ${labelClassName}`; + + const INPUT_CLASSNAME = `w-full h-full ${ + focused || value ? 'pt-6 pb-2' : 'py-2.5' + } px-3 text-input border-1 border-solid border-gray-300 rounded placeholder-medium text-input focus:ring-info ${ + isInvalid ? 'border-dark-red placeholder-dark-red' : '' + } ${inputClassName}`; + + const handleFocus = () => setFocused(true); + + const handleBlur = () => setFocused(false); + + return ( +
+ + +
+ ); + } +); diff --git a/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx new file mode 100644 index 000000000..a8539c51e --- /dev/null +++ b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx @@ -0,0 +1,68 @@ +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { CreateAccount } from './panes/CreateAccount'; +import { SignIn } from './panes/SignIn'; +import SNLogoFull from '../../svg/ic-sn-logo-full.svg'; + +type PaneSelectorProps = { + currentPane: PurchaseFlowPane; +} & PurchaseFlowViewProps; + +type PurchaseFlowViewProps = { + appState: AppState; + application: WebApplication; +}; + +const PurchaseFlowPaneSelector: FunctionComponent = ({ + currentPane, + appState, + application, +}) => { + switch (currentPane) { + case PurchaseFlowPane.CreateAccount: + return ; + case PurchaseFlowPane.SignIn: + return ; + } +}; + +export const PurchaseFlowView: FunctionComponent = + observer(({ appState, application }) => { + const { currentPane } = appState.purchaseFlow; + + return ( +
+
+
+ + +
+ +
+
+ ); + }); diff --git a/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx b/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx new file mode 100644 index 000000000..e1998f9d5 --- /dev/null +++ b/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx @@ -0,0 +1,33 @@ +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { isDesktopApplication } from '@/utils'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { PurchaseFlowView } from './PurchaseFlowView'; + +export type PurchaseFlowWrapperProps = { + appState: AppState; + application: WebApplication; +}; + +export const loadPurchaseFlowUrl = async ( + application: WebApplication +): Promise => { + const url = await application.getPurchaseFlowUrl(); + if (url) { + const currentUrl = window.location.href.split('/?')[0]; + const successUrl = isDesktopApplication() + ? `standardnotes://${currentUrl}` + : currentUrl; + window.location.assign(`${url}&success_url=${successUrl}`); + } +}; + +export const PurchaseFlowWrapper: FunctionComponent = + observer(({ appState, application }) => { + if (!appState.purchaseFlow.isOpen) { + return null; + } + + return ; + }); diff --git a/app/assets/javascripts/purchaseFlow/index.ts b/app/assets/javascripts/purchaseFlow/index.ts new file mode 100644 index 000000000..b82461d65 --- /dev/null +++ b/app/assets/javascripts/purchaseFlow/index.ts @@ -0,0 +1,8 @@ +import { toDirective } from '@/components/utils'; +import { + PurchaseFlowWrapper, + PurchaseFlowWrapperProps, +} from './PurchaseFlowWrapper'; + +export const PurchaseFlowDirective = + toDirective(PurchaseFlowWrapper); diff --git a/app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx b/app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx new file mode 100644 index 000000000..b7922cbc4 --- /dev/null +++ b/app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx @@ -0,0 +1,200 @@ +import { Button } from '@/components/Button'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import Illustration from '../../../svg/create-account-illustration.svg'; +import Circle from '../../../svg/circle-55.svg'; +import BlueDot from '../../../svg/blue-dot.svg'; +import Diamond from '../../../svg/diamond-with-horizontal-lines.svg'; +import { FloatingLabelInput } from '@/components/FloatingLabelInput'; +import { isEmailValid } from '@/utils'; +import { loadPurchaseFlowUrl } from '../PurchaseFlowWrapper'; + +type Props = { + appState: AppState; + application: WebApplication; +}; + +export const CreateAccount: FunctionComponent = observer( + ({ appState, application }) => { + const { setCurrentPane } = appState.purchaseFlow; + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isCreatingAccount, setIsCreatingAccount] = useState(false); + const [isEmailInvalid, setIsEmailInvalid] = useState(false); + const [isPasswordNotMatching, setIsPasswordNotMatching] = useState(false); + + const emailInputRef = useRef(); + const passwordInputRef = useRef(); + const confirmPasswordInputRef = useRef(); + + useEffect(() => { + if (emailInputRef.current) emailInputRef.current.focus(); + }, []); + + const handleEmailChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setEmail(e.target.value); + setIsEmailInvalid(false); + } + }; + + const handlePasswordChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setPassword(e.target.value); + } + }; + + const handleConfirmPasswordChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setConfirmPassword(e.target.value); + setIsPasswordNotMatching(false); + } + }; + + const handleSignInInstead = () => { + setCurrentPane(PurchaseFlowPane.SignIn); + }; + + const handleCreateAccount = async () => { + if (!email) { + emailInputRef?.current.focus(); + return; + } + + if (!isEmailValid(email)) { + setIsEmailInvalid(true); + emailInputRef?.current.focus(); + return; + } + + if (!password) { + passwordInputRef?.current.focus(); + return; + } + + if (!confirmPassword) { + confirmPasswordInputRef?.current.focus(); + return; + } + + if (password !== confirmPassword) { + setConfirmPassword(''); + setIsPasswordNotMatching(true); + confirmPasswordInputRef?.current.focus(); + return; + } + + setIsCreatingAccount(true); + + try { + const response = await application.register(email, password); + if (response.error || response.data?.error) { + throw new Error( + response.error?.message || response.data?.error?.message + ); + } else { + loadPurchaseFlowUrl(application).catch((err) => { + console.error(err); + application.alertService.alert(err); + }); + } + } catch (err) { + console.error(err); + application.alertService.alert(err as string); + } finally { + setIsCreatingAccount(false); + } + }; + + return ( +
+ + + + + + + + +
+

Create your free account

+
+ to continue to Standard Notes. +
+
+
+ + {isEmailInvalid ? ( +
+ Please provide a valid email. +
+ ) : null} + + + {isPasswordNotMatching ? ( +
+ Passwords don't match. Please try again. +
+ ) : null} +
+
+
+ +
+
+ +
+ ); + } +); diff --git a/app/assets/javascripts/purchaseFlow/panes/SignIn.tsx b/app/assets/javascripts/purchaseFlow/panes/SignIn.tsx new file mode 100644 index 000000000..565eb236c --- /dev/null +++ b/app/assets/javascripts/purchaseFlow/panes/SignIn.tsx @@ -0,0 +1,175 @@ +import { Button } from '@/components/Button'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import Circle from '../../../svg/circle-55.svg'; +import BlueDot from '../../../svg/blue-dot.svg'; +import Diamond from '../../../svg/diamond-with-horizontal-lines.svg'; +import { FloatingLabelInput } from '@/components/FloatingLabelInput'; +import { isEmailValid } from '@/utils'; +import { loadPurchaseFlowUrl } from '../PurchaseFlowWrapper'; + +type Props = { + appState: AppState; + application: WebApplication; +}; + +export const SignIn: FunctionComponent = observer( + ({ appState, application }) => { + const { setCurrentPane } = appState.purchaseFlow; + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isSigningIn, setIsSigningIn] = useState(false); + const [isEmailInvalid, setIsEmailInvalid] = useState(false); + const [isPasswordInvalid, setIsPasswordInvalid] = useState(false); + const [otherErrorMessage, setOtherErrorMessage] = useState(''); + + const emailInputRef = useRef(); + const passwordInputRef = useRef(); + + useEffect(() => { + if (emailInputRef.current) emailInputRef.current.focus(); + }, []); + + const handleEmailChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setEmail(e.target.value); + setIsEmailInvalid(false); + } + }; + + const handlePasswordChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setPassword(e.target.value); + setIsPasswordInvalid(false); + setOtherErrorMessage(''); + } + }; + + const handleCreateAccountInstead = () => { + if (isSigningIn) return; + setCurrentPane(PurchaseFlowPane.CreateAccount); + }; + + const handleSignIn = async () => { + if (!email) { + emailInputRef?.current.focus(); + return; + } + + if (!isEmailValid(email)) { + setIsEmailInvalid(true); + emailInputRef?.current.focus(); + return; + } + + if (!password) { + passwordInputRef?.current.focus(); + return; + } + + setIsSigningIn(true); + + try { + const response = await application.signIn(email, password); + if (response.error || response.data?.error) { + throw new Error( + response.error?.message || response.data?.error?.message + ); + } else { + loadPurchaseFlowUrl(application).catch((err) => { + console.error(err); + application.alertService.alert(err); + }); + } + } catch (err) { + console.error(err); + if ((err as Error).toString().includes('Invalid email or password')) { + setIsSigningIn(false); + setIsEmailInvalid(true); + setIsPasswordInvalid(true); + setOtherErrorMessage('Invalid email or password.'); + setPassword(''); + } else { + application.alertService.alert(err as string); + } + } + }; + + return ( +
+ + + + + + + + +
+

Sign in

+
+ to continue to Standard Notes. +
+
+
+ + {isEmailInvalid && !otherErrorMessage ? ( +
+ Please provide a valid email. +
+ ) : null} + + {otherErrorMessage ? ( +
{otherErrorMessage}
+ ) : null} +
+
+
+ ); + } +); 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 ad3b87ae3..ee5c2ac87 100644 --- a/app/assets/javascripts/ui_models/app_state/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state/app_state.ts @@ -23,6 +23,7 @@ import { NotesState } from './notes_state'; import { TagsState } from './tags_state'; import { AccountMenuState } from '@/ui_models/app_state/account_menu_state'; import { PreferencesState } from './preferences_state'; +import { PurchaseFlowState } from './purchase_flow_state'; import { QuickSettingsState } from './quick_settings_state'; export enum AppStateEvent { @@ -67,6 +68,7 @@ export class AppState { readonly accountMenu: AccountMenuState; readonly actionsMenu = new ActionsMenuState(); readonly preferences = new PreferencesState(); + readonly purchaseFlow: PurchaseFlowState; readonly noAccountWarning: NoAccountWarningState; readonly noteTags: NoteTagsState; readonly sync = new SyncState(); @@ -113,6 +115,7 @@ export class AppState { application, this.appEventObserverRemovers ); + this.purchaseFlow = new PurchaseFlowState(application); this.addAppEventObserver(); this.streamNotesAndTags(); this.onVisibilityChange = () => { @@ -284,6 +287,8 @@ export class AppState { break; case ApplicationEvent.Launched: this.locked = false; + if (window.location.search.includes('purchase=true')) + this.purchaseFlow.openPurchaseFlow(); break; case ApplicationEvent.SyncStatusChanged: this.sync.update(this.application.getSyncStatus()); diff --git a/app/assets/javascripts/ui_models/app_state/purchase_flow_state.ts b/app/assets/javascripts/ui_models/app_state/purchase_flow_state.ts new file mode 100644 index 000000000..3beed9b5a --- /dev/null +++ b/app/assets/javascripts/ui_models/app_state/purchase_flow_state.ts @@ -0,0 +1,38 @@ +import { action, makeObservable, observable } from 'mobx'; +import { WebApplication } from '../application'; + +export enum PurchaseFlowPane { + SignIn, + CreateAccount, +} + +export class PurchaseFlowState { + isOpen = false; + currentPane = PurchaseFlowPane.CreateAccount; + + constructor(private application: WebApplication) { + makeObservable(this, { + isOpen: observable, + currentPane: observable, + + setCurrentPane: action, + openPurchaseFlow: action, + closePurchaseFlow: action, + }); + } + + setCurrentPane = (currentPane: PurchaseFlowPane): void => { + this.currentPane = currentPane; + }; + + openPurchaseFlow = (): void => { + const user = this.application.getUser(); + if (!user) { + this.isOpen = true; + } + }; + + closePurchaseFlow = (): void => { + this.isOpen = false; + }; +} diff --git a/app/assets/javascripts/views/application/application-view.pug b/app/assets/javascripts/views/application/application-view.pug index 9bd688c89..785ce2df5 100644 --- a/app/assets/javascripts/views/application/application-view.pug +++ b/app/assets/javascripts/views/application/application-view.pug @@ -41,4 +41,7 @@ application='self.application' app-state='self.appState' ) - + purchase-flow( + application='self.application' + app-state='self.appState' + ) diff --git a/app/assets/stylesheets/_main.scss b/app/assets/stylesheets/_main.scss index b54211c3a..a6b059eb2 100644 --- a/app/assets/stylesheets/_main.scss +++ b/app/assets/stylesheets/_main.scss @@ -14,6 +14,8 @@ $z-index-footer-bar-item-panel: 2000; $z-index-preferences: 3000; +$z-index-purchase-flow: 4000; + $z-index-lock-screen: 10000; $z-index-modal: 10000; @@ -244,3 +246,7 @@ $footer-height: 2rem; .z-index-preferences { z-index: $z-index-preferences; } + +.z-index-purchase-flow { + z-index: $z-index-purchase-flow; +} diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index dd594c2f1..aa520fac6 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -1,9 +1,21 @@ /* Components and utilities that are good candidates for extraction to StyleKit. */ +:root { + --sn-stylekit-grey-2: #f8f9fc; +} + +.bg-grey-2 { + background-color: var(--sn-stylekit-grey-2); +} + .h-90vh { height: 90vh; } +.h-26 { + width: 6.5rem; +} + .h-33 { height: 8.25rem; } @@ -219,6 +231,10 @@ margin-right: 0.75rem; } +.mr-12 { + margin-right: 3rem; +} + .my-0\.5 { margin-top: 0.125rem; margin-bottom: 0.125rem; @@ -234,14 +250,30 @@ margin-bottom: 1rem; } +.mt-0 { + margin-top: 0; +} + .mb-2 { margin-bottom: 0.5rem; } +.mb-4 { + margin-bottom: 1rem; +} + +.mb-5 { + margin-bottom: 1.25rem; +} + .max-w-89 { max-width: 22.25rem; } +.w-26 { + width: 6.5rem; +} + .w-92 { width: 23rem; } @@ -274,6 +306,18 @@ min-width: 3.75rem; } +.min-w-24 { + min-width: 6rem; +} + +.min-w-30 { + min-width: 7.5rem; +} + +.min-w-90 { + min-width: 22.5rem; +} + .min-h-1px { min-height: 1px; } @@ -314,6 +358,18 @@ color: var(--sn-stylekit-background-color); } +.p-1 { + padding: 0.25rem; +} + +.p-8 { + padding: 2rem; +} + +.p-12 { + padding: 3rem; +} + .pt-1 { padding-top: 0.25rem; } @@ -326,10 +382,26 @@ padding-top: 0.5rem; } +.pt-3 { + padding-top: 0.75rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + .pb-1 { padding-bottom: 0.25rem; } +.pb-2 { + padding-bottom: 0.5rem; +} + +.pb-2\.5 { + padding-bottom: 0.625rem; +} + .px-9 { padding-left: 2.25rem; padding-right: 2.25rem; @@ -340,6 +412,11 @@ padding-right: 3rem; } +.sn-component .py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + .py-9 { padding-top: 2.25rem; padding-bottom: 2.25rem; @@ -349,6 +426,118 @@ user-select: none; } +.placeholder-dark-red::placeholder { + @extend .color-dark-red; +} + +.placeholder-medium::placeholder { + font-weight: 500; +} + +.top-30\% { + top: 30%; +} + +.top-35\% { + top: 35%; +} + +.top-40\% { + top: 40%; +} + +.-top-0\.25 { + top: -0.0625rem; +} + +.bottom-20\% { + bottom: 20%; +} + +.bottom-25\% { + bottom: 25%; +} + +.bottom-30\% { + bottom: 30%; +} + +.bottom-35\% { + bottom: 35%; +} + +.bottom-40\% { + bottom: 40%; +} + +.left-2 { + left: 0.5rem; +} + +.-left-10 { + left: -2.5rem; +} + +.-left-28 { + left: -7rem; +} + +.-left-16 { + left: -4rem; +} + +.-left-40 { + left: -10rem; +} + +.-left-56 { + left: -14rem; +} + +.-right-2 { + right: -0.5rem; +} + +.-right-10 { + right: -2.5rem; +} + +.-right-20 { + right: -5rem; +} + +.-right-24 { + right: -6rem; +} + +.-right-44 { + right: -11rem; +} + +.-right-56 { + right: -14rem; +} + +.-translate-x-1\/2 { + transform: translateX(-50%); +} + +.-translate-y-1\/2 { + transform: translateY(-50%); +} + +.translate-x-1\/2 { + transform: translateX(50%); +} + +.-bottom-5 { + bottom: -1.25rem; +} + +.-z-index-1 { + z-index: -1; +} + .sn-component .btn-w-full { width: 100%; } diff --git a/app/assets/svg/blue-dot.svg b/app/assets/svg/blue-dot.svg new file mode 100644 index 000000000..dacc6754e --- /dev/null +++ b/app/assets/svg/blue-dot.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/svg/circle-55.svg b/app/assets/svg/circle-55.svg new file mode 100644 index 000000000..e0ca48fbe --- /dev/null +++ b/app/assets/svg/circle-55.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/assets/svg/create-account-illustration.svg b/app/assets/svg/create-account-illustration.svg new file mode 100644 index 000000000..1e06d2b5e --- /dev/null +++ b/app/assets/svg/create-account-illustration.svg @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/svg/diamond-with-horizontal-lines.svg b/app/assets/svg/diamond-with-horizontal-lines.svg new file mode 100644 index 000000000..b05e2d28c --- /dev/null +++ b/app/assets/svg/diamond-with-horizontal-lines.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/assets/svg/ic-sn-logo-full.svg b/app/assets/svg/ic-sn-logo-full.svg new file mode 100644 index 000000000..f1e356972 --- /dev/null +++ b/app/assets/svg/ic-sn-logo-full.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 3a4e2509af94bdf02048a53ca5091d9f6a27ac03 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 19 Oct 2021 21:13:20 +0530 Subject: [PATCH 10/53] feat: Add "Export" and "Duplicate" buttons in notes options menu. (#688) Co-authored-by: Mough --- app/assets/icons/ic-copy.svg | 2 +- app/assets/icons/ic-download.svg | 2 +- .../javascripts/components/NotesOptions.tsx | 36 ++++++++++++++ app/assets/javascripts/enums.ts | 2 +- .../preferences/panes/Extensions.tsx | 1 - .../panes/general-segments/Defaults.tsx | 48 ++++++++----------- .../javascripts/views/notes/notes_view.ts | 2 + package.json | 4 +- yarn.lock | 25 ++++------ 9 files changed, 72 insertions(+), 50 deletions(-) diff --git a/app/assets/icons/ic-copy.svg b/app/assets/icons/ic-copy.svg index 694626a33..98e2c93e6 100644 --- a/app/assets/icons/ic-copy.svg +++ b/app/assets/icons/ic-copy.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-download.svg b/app/assets/icons/ic-download.svg index 923b753bd..e87e65165 100644 --- a/app/assets/icons/ic-download.svg +++ b/app/assets/icons/ic-download.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/javascripts/components/NotesOptions.tsx b/app/assets/javascripts/components/NotesOptions.tsx index d95548af5..60aada1d6 100644 --- a/app/assets/javascripts/components/NotesOptions.tsx +++ b/app/assets/javascripts/components/NotesOptions.tsx @@ -205,6 +205,26 @@ export const NotesOptions = observer( setTagsMenuOpen(!tagsMenuOpen); }; + const downloadSelectedItems = () => { + notes.forEach((note) => { + const editor = application.componentManager.editorForNote(note); + const format = editor?.package_info?.file_type || 'txt'; + const downloadAnchor = document.createElement('a'); + downloadAnchor.setAttribute( + 'href', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(note.text) + ); + downloadAnchor.setAttribute('download', `${note.title}.${format}`); + downloadAnchor.click(); + }); + }; + + const duplicateSelectedItems = () => { + notes.forEach((note) => { + application.duplicateItem(note); + }); + }; + return ( <> )} + + {unarchived && ( + ); + } +); + +export const MenuItemSeparator: FunctionComponent = () => ( +
+); + +type ListElementProps = { + isFirstMenuItem: boolean; + children: ComponentChildren; +}; + +export const MenuItemListElement: FunctionComponent = + forwardRef(({ children, isFirstMenuItem }, ref: Ref) => { + const child = children as VNode; + + return ( +
  • + {{ + ...child, + props: { + ...(child.props ? { ...child.props } : {}), + ...(child.type === MenuItem + ? { + tabIndex: isFirstMenuItem ? 0 : -1, + } + : {}), + }, + }} +
  • + ); + }); diff --git a/app/assets/javascripts/views/notes/notes-view.pug b/app/assets/javascripts/views/notes/notes-view.pug index c05a8cd59..d373eb36a 100644 --- a/app/assets/javascripts/views/notes/notes-view.pug +++ b/app/assets/javascripts/views/notes/notes-view.pug @@ -49,71 +49,12 @@ | Options .sk-app-bar-item-column .sk-sublabel {{self.optionsSubtitle()}} - #notes-options-menu.sk-menu-panel.dropdown-menu( - ng-show='self.state.mutable.showMenu' - ) - .sk-menu-panel-header - .sk-menu-panel-header-title Sort By - a.info.sk-h5(ng-click='self.toggleReverseSort()') - | {{self.state.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}} - menu-row( - action="self.selectedMenuItem(); self.selectedSortByCreated()" - circle="self.state.sortBy == 'created_at' && 'success'" - desc="'Sort notes by newest first'" - label="'Date Added'" - ) - menu-row( - action="self.selectedMenuItem(); self.selectedSortByUpdated()" - circle="self.state.sortBy == 'userModifiedDate' && 'success'" - desc="'Sort notes with the most recently updated first'" - label="'Date Modified'" - ) - menu-row( - action="self.selectedMenuItem(); self.selectedSortByTitle()" - circle="self.state.sortBy == 'title' && 'success'" - desc="'Sort notes alphabetically by their title'" - label="'Title'" - ) - .sk-menu-panel-section - .sk-menu-panel-header - .sk-menu-panel-header-title Display - menu-row( - action="self.selectedMenuItem(); self.togglePrefKey('showArchived')" - circle="self.state.showArchived ? 'success' : 'danger'" - desc=`'Archived notes are usually hidden. - You can explicitly show them with this option.'` - faded="!self.state.showArchived" - label="'Archived Notes'" - ) - menu-row( - action="self.selectedMenuItem(); self.togglePrefKey('hidePinned')" - circle="self.state.hidePinned ? 'danger' : 'success'" - desc=`'Pinned notes always appear on top. You can hide them temporarily - with this option so you can focus on other notes in the list.'` - faded="self.state.hidePinned" - label="'Pinned Notes'" - ) - menu-row( - action="self.selectedMenuItem(); self.togglePrefKey('hideNotePreview')" - circle="self.state.hideNotePreview ? 'danger' : 'success'" - desc="'Hide the note preview for a more condensed list of notes'" - faded="self.state.hideNotePreview" - label="'Note Preview'" - ) - menu-row( - action="self.selectedMenuItem(); self.togglePrefKey('hideDate')" - circle="self.state.hideDate ? 'danger' : 'success'" - desc="'Hide the date displayed in each row'" - faded="self.state.hideDate" - label="'Date'" - ) - menu-row( - action="self.selectedMenuItem(); self.togglePrefKey('hideTags')" - circle="self.state.hideTags ? 'danger' : 'success'" - desc="'Hide the list of tags associated with each note'" - faded="self.state.hideTags" - label="'Tags'" - ) + notes-list-options-menu( + ng-if='self.state.mutable.showMenu' + app-state='self.appState' + application='self.application' + set-show-menu-false='self.setShowMenuFalse' + ) p.empty-notes-list.faded( ng-if="self.state.completedFullSync && !self.state.renderedNotes.length" ) No notes. diff --git a/app/assets/javascripts/views/notes/notes_view.ts b/app/assets/javascripts/views/notes/notes_view.ts index 6ab6e3348..d8b69852b 100644 --- a/app/assets/javascripts/views/notes/notes_view.ts +++ b/app/assets/javascripts/views/notes/notes_view.ts @@ -96,6 +96,7 @@ class NotesViewCtrl extends PureViewCtrl { this.onWindowResize = this.onWindowResize.bind(this); this.onPanelResize = this.onPanelResize.bind(this); this.onPanelWidthEvent = this.onPanelWidthEvent.bind(this); + this.setShowMenuFalse = this.setShowMenuFalse.bind(this); window.addEventListener('resize', this.onWindowResize, true); this.registerKeyboardShortcuts(); this.autorun(async () => { @@ -441,7 +442,7 @@ class NotesViewCtrl extends PureViewCtrl { includeTrashed = this.state.searchOptions.includeTrashed; } else { includeArchived = this.state.showArchived ?? false; - includeTrashed = false; + includeTrashed = this.state.showTrashed ?? false; } const criteria = NotesDisplayCriteria.Create({ @@ -451,6 +452,7 @@ class NotesViewCtrl extends PureViewCtrl { includeArchived, includeTrashed, includePinned: !this.state.hidePinned, + includeProtected: !this.state.hideProtected, searchQuery: { query: searchText, includeProtectedNoteText: this.state.searchOptions.includeProtectedContents @@ -554,10 +556,18 @@ class NotesViewCtrl extends PureViewCtrl { PrefKey.NotesShowArchived, false ); + viewOptions.showTrashed = this.application.getPreference( + PrefKey.NotesShowTrashed, + false + ) as boolean; viewOptions.hidePinned = this.application.getPreference( PrefKey.NotesHidePinned, false ); + viewOptions.hideProtected = this.application.getPreference( + PrefKey.NotesHideProtected, + false + ); viewOptions.hideNotePreview = this.application.getPreference( PrefKey.NotesHideNotePreview, false @@ -576,6 +586,8 @@ class NotesViewCtrl extends PureViewCtrl { viewOptions.sortReverse !== state.sortReverse || viewOptions.hidePinned !== state.hidePinned || viewOptions.showArchived !== state.showArchived || + viewOptions.showTrashed !== state.showTrashed || + viewOptions.hideProtected !== state.hideProtected || viewOptions.hideTags !== state.hideTags ); await this.setNotesState({ @@ -672,9 +684,15 @@ class NotesViewCtrl extends PureViewCtrl { if (this.state.showArchived) { base += " | + Archived"; } + if (this.state.showTrashed) { + base += " | + Trashed"; + } if (this.state.hidePinned) { base += " | – Pinned"; } + if (this.state.hideProtected) { + base += " | – Protected"; + } if (this.state.sortReverse) { base += " | Reversed"; } diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index aa520fac6..259767b39 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -258,6 +258,14 @@ margin-bottom: 0.5rem; } +.max-w-3\/4 { + max-width: 75%; +} + +.max-w-72 { + max-width: 18rem; +} + .mb-4 { margin-bottom: 1rem; } @@ -306,6 +314,10 @@ min-width: 3.75rem; } +.min-w-70 { + min-width: 17.5rem; +} + .min-w-24 { min-width: 6rem; } @@ -582,6 +594,10 @@ top: 50%; } +.left-2 { + left: 0.5rem; +} + .left-1\/2 { left: 50%; } @@ -615,3 +631,7 @@ .focus\:bg-info-backdrop:focus { background-color: var(--sn-stylekit-info-backdrop-color); } + +.list-style-none { + list-style-type: none; +} From e79811afdad268909de97c46b3e9b27780079cc5 Mon Sep 17 00:00:00 2001 From: Mo Date: Thu, 21 Oct 2021 12:41:58 -0500 Subject: [PATCH 12/53] feat: add purchase url (#695) * feat: add purchase url * chore: bump snjs * fix: reuse existing function --- .env.sample | 1 + app/assets/javascripts/app.ts | 9 ++++++++ .../account/subscription/NoSubscription.tsx | 9 ++------ .../purchaseFlow/PurchaseFlowWrapper.tsx | 22 +++++++++++++------ .../javascripts/services/errorReporting.ts | 7 ------ app/views/application/app.html.erb | 1 + index.html | 2 ++ package.json | 2 +- 8 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.env.sample b/.env.sample index 5033e80ba..8626915d2 100644 --- a/.env.sample +++ b/.env.sample @@ -7,6 +7,7 @@ RAILS_LOG_LEVEL=INFO RAILS_SERVE_STATIC_FILES=true SECRET_KEY_BASE=test APP_HOST=http://localhost:3001 +PURCHASE_URL=https://standardnotes.com/purchase EXTENSIONS_MANAGER_LOCATION=extensions/extensions-manager/dist/index.html SF_DEFAULT_SERVER=http://localhost:3000 diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index ddd208fab..ee83819e6 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -1,5 +1,14 @@ 'use strict'; +declare global { + interface Window { + // eslint-disable-next-line camelcase + _bugsnag_api_key?: string; + // eslint-disable-next-line camelcase + _purchase_url?: string; + } +} + import { SNLog } from '@standardnotes/snjs'; import angular from 'angular'; import { configRoutes } from './routes'; diff --git a/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx b/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx index ca5a1c953..f76e74ebf 100644 --- a/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx +++ b/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx @@ -3,7 +3,7 @@ import { LinkButton, Text } from '@/preferences/components'; import { Button } from '@/components/Button'; import { WebApplication } from "@/ui_models/application"; import { useState } from "preact/hooks"; -import { isDesktopApplication } from "@/utils"; +import { loadPurchaseFlowUrl } from "@/purchaseFlow/PurchaseFlowWrapper"; export const NoSubscription: FunctionalComponent<{ application: WebApplication; @@ -15,12 +15,7 @@ export const NoSubscription: FunctionalComponent<{ const errorMessage = 'There was an error when attempting to redirect you to the subscription page.'; setIsLoadingPurchaseFlow(true); try { - const url = await application.getPurchaseFlowUrl(); - if (url) { - const currentUrl = window.location.href; - const successUrl = isDesktopApplication() ? `standardnotes://${currentUrl}` : currentUrl; - window.location.assign(`${url}&success_url=${successUrl}`); - } else { + if (!await loadPurchaseFlowUrl(application)) { setPurchaseFlowError(errorMessage); } } catch (e) { diff --git a/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx b/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx index e1998f9d5..361e3dc9f 100644 --- a/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx +++ b/app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx @@ -10,17 +10,25 @@ export type PurchaseFlowWrapperProps = { application: WebApplication; }; +export const getPurchaseFlowUrl = async (application: WebApplication): Promise => { + const token = await application.getNewSubscriptionToken(); + if (token) { + const currentUrl = window.location.href; + const successUrl = isDesktopApplication() ? `standardnotes://${currentUrl}` : currentUrl; + return `${window._purchase_url}?subscription_token=${token}&success_url=${successUrl}`; + } + return undefined; +}; + export const loadPurchaseFlowUrl = async ( application: WebApplication -): Promise => { - const url = await application.getPurchaseFlowUrl(); +): Promise => { + const url = await getPurchaseFlowUrl(application); if (url) { - const currentUrl = window.location.href.split('/?')[0]; - const successUrl = isDesktopApplication() - ? `standardnotes://${currentUrl}` - : currentUrl; - window.location.assign(`${url}&success_url=${successUrl}`); + window.location.assign(url); + return true; } + return false; }; export const PurchaseFlowWrapper: FunctionComponent = diff --git a/app/assets/javascripts/services/errorReporting.ts b/app/assets/javascripts/services/errorReporting.ts index 16c70c867..cb748d164 100644 --- a/app/assets/javascripts/services/errorReporting.ts +++ b/app/assets/javascripts/services/errorReporting.ts @@ -5,13 +5,6 @@ import Bugsnag from '@bugsnag/js'; import { WebCrypto } from '../crypto'; import { AppVersion } from '@/version'; -declare global { - interface Window { - // eslint-disable-next-line camelcase - _bugsnag_api_key?: string; - } -} - function redactFilePath(line: string): string { const fileName = line.match(/\w+\.(html|js)/)?.[0]; const redacted = ''; diff --git a/app/views/application/app.html.erb b/app/views/application/app.html.erb index e92ab8c83..c13c3c7d1 100644 --- a/app/views/application/app.html.erb +++ b/app/views/application/app.html.erb @@ -37,6 +37,7 @@ window._bugsnag_api_key = "<%= ENV['BUGSNAG_API_KEY'] %>"; window._enable_unfinished_features = "<%= ENV['ENABLE_UNFINISHED_FEATURES'] %>" === 'true'; window._websocket_url = "<%= ENV['WEBSOCKET_URL'] %>"; + window._purchase_url = "<%= ENV['PURCHASE_URL'] %>"; <% if Rails.env.development? %> diff --git a/index.html b/index.html index 691b2da48..2826c1918 100644 --- a/index.html +++ b/index.html @@ -34,6 +34,7 @@ data-bugsnag-api-key="<%= env.DEV_BUGSNAG_API_KEY %>" data-enable-unfinished-features="<%= env.ENABLE_UNFINISHED_FEATURES %>" data-web-socket-url="<%= env.DEV_WEBSOCKET_URL %>" + data-purchase-url="<%= env.PURCHASE_URL %>" > diff --git a/package.json b/package.json index a8e0daa13..e0b8813b2 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@reach/listbox": "^0.16.1", "@standardnotes/features": "1.7.2", "@standardnotes/sncrypto-web": "1.5.2", - "@standardnotes/snjs": "2.15.2", + "@standardnotes/snjs": "2.16.0", "mobx": "^6.3.2", "mobx-react-lite": "^3.2.0", "preact": "^10.5.12", From 8d9b192b81e13d385fd4664e2a5a1a1d97b32d95 Mon Sep 17 00:00:00 2001 From: Mo Date: Thu, 21 Oct 2021 13:06:49 -0500 Subject: [PATCH 13/53] feat: remove extensions manager (#696) * feat: remove extensions manager * fix: remove unused code Co-authored-by: Aman Harwara --- .env.sample | 2 - .github/workflows/beta.yml | 3 - .github/workflows/dev.yml | 3 - .github/workflows/prod.yml | 3 - .gitmodules | 10 -- Gemfile.lock | 3 + README.md | 10 -- .../javascripts/services/nativeExtManager.ts | 118 --------------- .../javascripts/ui_models/application.ts | 6 - .../ui_models/application_group.ts | 3 - .../javascripts/views/footer/footer-view.pug | 32 ---- .../javascripts/views/footer/footer_view.ts | 142 +----------------- app/extensions/extensions-manager | 1 - app/views/application/app.html.erb | 2 - index.html | 2 - package.json | 3 +- public/extensions/extensions-manager | 1 - 17 files changed, 12 insertions(+), 332 deletions(-) delete mode 100644 app/assets/javascripts/services/nativeExtManager.ts delete mode 160000 app/extensions/extensions-manager delete mode 160000 public/extensions/extensions-manager diff --git a/.env.sample b/.env.sample index 8626915d2..21b6626a9 100644 --- a/.env.sample +++ b/.env.sample @@ -9,12 +9,10 @@ SECRET_KEY_BASE=test APP_HOST=http://localhost:3001 PURCHASE_URL=https://standardnotes.com/purchase -EXTENSIONS_MANAGER_LOCATION=extensions/extensions-manager/dist/index.html SF_DEFAULT_SERVER=http://localhost:3000 # Development options DEV_DEFAULT_SYNC_SERVER=https://api.standardnotes.com -DEV_EXTENSIONS_MANAGER_LOCATION=public/extensions/extensions-manager/dist/index.html ENABLE_UNFINISHED_FEATURES=false DEV_WEBSOCKET_URL=wss://sockets-dev.standardnotes.com diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 183b866c2..d8dee60ec 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -31,9 +31,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Initiate submodules - run: git submodule update --init - - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@master with: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 32a685848..088b40ed7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -35,9 +35,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Initiate submodules - run: git submodule update --init - - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@master with: diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 04b9144b3..f1ebce04e 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -35,9 +35,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Initiate submodules - run: git submodule update --init - - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@master with: diff --git a/.gitmodules b/.gitmodules index adb7eaeaa..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +0,0 @@ -[submodule "vendor/extensions/extensions-manager"] - path = vendor/extensions/extensions-manager - url = https://github.com/sn-extensions/extensions-manager.git -[submodule "app/extensions/extensions-manager"] - path = app/extensions/extensions-manager - url = https://github.com/sn-extensions/extensions-manager.git -[submodule "public/extensions/extensions-manager"] - path = public/extensions/extensions-manager - url = https://github.com/sn-extensions/extensions-manager.git - diff --git a/Gemfile.lock b/Gemfile.lock index d2fe3cd95..e6dbdab88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,6 +107,8 @@ GEM racc (~> 1.4) nokogiri (1.11.1-x64-mingw32) racc (~> 1.4) + nokogiri (1.11.1-x86_64-darwin) + racc (~> 1.4) non-stupid-digest-assets (1.0.9) sprockets (>= 2.0) puma (4.3.5) @@ -200,6 +202,7 @@ GEM PLATFORMS ruby x64-mingw32 + x86_64-darwin-18 DEPENDENCIES byebug diff --git a/README.md b/README.md index e623f0f1f..e7512f18a 100644 --- a/README.md +++ b/README.md @@ -97,16 +97,6 @@ Then open your browser to `http://localhost:3001`. --- -**Extensions Manager and Batch Manager:** - -The web app makes use of two optional native extensions, which, when running the app with Rails, can be configured to work as follows: - -1. `git submodule update --init` (will load the submodules in the `public/extensions` folder) -1. Set the following environment variables in the .env file: - ``` - EXTENSIONS_MANAGER_LOCATION=extensions/extensions-manager/dist/index.html - ``` - You can also set the `SF_DEFAULT_SERVER` environment variable to set the default server for login and registration. ``` diff --git a/app/assets/javascripts/services/nativeExtManager.ts b/app/assets/javascripts/services/nativeExtManager.ts deleted file mode 100644 index 890bc454b..000000000 --- a/app/assets/javascripts/services/nativeExtManager.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { isDesktopApplication } from '@/utils'; -import { - SNPredicate, - ContentType, - SNComponent, - ApplicationService, - ComponentAction, - FillItemContent, - ComponentMutator, - Copy, - PayloadContent, - ComponentPermission } from '@standardnotes/snjs'; - -/** A class for handling installation of system extensions */ -export class NativeExtManager extends ApplicationService { - extManagerId = 'org.standardnotes.extensions-manager'; - - /** @override */ - async onAppLaunch() { - super.onAppLaunch(); - this.reload(); - } - - get extManagerPred() { - const extManagerId = 'org.standardnotes.extensions-manager'; - return SNPredicate.CompoundPredicate([ - new SNPredicate('content_type', '=', ContentType.Component), - new SNPredicate('package_info.identifier', '=', extManagerId) - ]); - } - - get extMgrUrl() { - return (window as any)._extensions_manager_location; - } - - reload() { - this.application!.singletonManager!.registerPredicate(this.extManagerPred); - this.resolveExtensionsManager(); - } - - async resolveExtensionsManager() { - const extensionsManager = (await this.application!.singletonManager!.findOrCreateSingleton( - this.extManagerPred, - ContentType.Component, - this.extensionsManagerTemplateContent() - )) as SNComponent; - let needsSync = false; - if (isDesktopApplication()) { - if (!extensionsManager.local_url) { - await this.application!.changeItem(extensionsManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.local_url = this.extMgrUrl; - }); - needsSync = true; - } - } else { - if (!extensionsManager.hosted_url) { - await this.application!.changeItem(extensionsManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.hosted_url = this.extMgrUrl; - }); - needsSync = true; - } - } - // Handle addition of SN|ExtensionRepo permission - const permissions = Copy(extensionsManager!.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(extensionsManager.uuid, (m) => { - const mutator = m as ComponentMutator; - mutator.permissions = permissions; - }); - needsSync = true; - } - if (needsSync) { - this.application!.saveItem(extensionsManager.uuid); - } - } - - extensionsManagerTemplateContent() { - const url = this.extMgrUrl; - if (!url) { - throw Error('this.extMgrUrl must be set.'); - } - const packageInfo = { - name: 'Extensions', - identifier: this.extManagerId - }; - const content = FillItemContent({ - name: packageInfo.name, - area: 'rooms', - package_info: packageInfo, - permissions: [ - { - name: ComponentAction.StreamItems, - content_types: [ - ContentType.Component, - ContentType.Theme, - ContentType.ServerExtension, - ContentType.ActionsExtension, - ContentType.Mfa, - ContentType.Editor, - ContentType.ExtensionRepo - ] - } - ] - }) as PayloadContent; - if (isDesktopApplication()) { - content.local_url = this.extMgrUrl; - } else { - content.hosted_url = this.extMgrUrl; - } - return content; - } -} diff --git a/app/assets/javascripts/ui_models/application.ts b/app/assets/javascripts/ui_models/application.ts index a11dd17d4..fbf1f7bad 100644 --- a/app/assets/javascripts/ui_models/application.ts +++ b/app/assets/javascripts/ui_models/application.ts @@ -21,7 +21,6 @@ import { AutolockService } from '@/services/autolock_service'; import { ArchiveManager } from '@/services/archiveManager'; import { DesktopManager } from '@/services/desktopManager'; import { IOService } from '@/services/ioService'; -import { NativeExtManager } from '@/services/nativeExtManager'; import { StatusManager } from '@/services/statusManager'; import { ThemeManager } from '@/services/themeManager'; import { AppVersion } from '@/version'; @@ -32,7 +31,6 @@ type WebServices = { desktopService: DesktopManager; autolockService: AutolockService; archiveService: ArchiveManager; - nativeExtService: NativeExtManager; statusManager: StatusManager; themeService: ThemeManager; io: IOService; @@ -133,10 +131,6 @@ export class WebApplication extends SNApplication { return this.webServices.archiveService; } - public getNativeExtService() { - return this.webServices.nativeExtService; - } - getStatusManager() { return this.webServices.statusManager; } diff --git a/app/assets/javascripts/ui_models/application_group.ts b/app/assets/javascripts/ui_models/application_group.ts index b9230eb1f..5b422a569 100644 --- a/app/assets/javascripts/ui_models/application_group.ts +++ b/app/assets/javascripts/ui_models/application_group.ts @@ -14,7 +14,6 @@ import { DesktopManager } from '@/services/desktopManager'; import { IOService } from '@/services/ioService'; import { AutolockService } from '@/services/autolock_service'; import { StatusManager } from '@/services/statusManager'; -import { NativeExtManager } from '@/services/nativeExtManager'; import { ThemeManager } from '@/services/themeManager'; export class ApplicationGroup extends SNApplicationGroup { @@ -85,7 +84,6 @@ export class ApplicationGroup extends SNApplicationGroup { platform === Platform.MacWeb || platform === Platform.MacDesktop ); const autolockService = new AutolockService(application); - const nativeExtService = new NativeExtManager(application); const statusManager = new StatusManager(); const themeService = new ThemeManager(application); application.setWebServices({ @@ -94,7 +92,6 @@ export class ApplicationGroup extends SNApplicationGroup { desktopService, io, autolockService, - nativeExtService, statusManager, themeService, }); diff --git a/app/assets/javascripts/views/footer/footer-view.pug b/app/assets/javascripts/views/footer/footer-view.pug index e24019f37..f4e95fbe3 100644 --- a/app/assets/javascripts/views/footer/footer-view.pug +++ b/app/assets/javascripts/views/footer/footer-view.pug @@ -39,23 +39,6 @@ app-state='ctrl.appState' application='ctrl.application' ng-if='ctrl.showQuickSettingsMenu',) - .sk-app-bar-item - a.no-decoration.sk-label.title( - href='https://standardnotes.com/help', - rel='noopener', - target='_blank' - ) - | Help - .sk-app-bar-item.border - .sk-app-bar-item(ng-repeat='room in ctrl.rooms track by room.uuid') - .sk-app-bar-item-column(ng-click='ctrl.selectRoom(room)') - .sk-label {{room.name}} - component-modal( - component-uuid='room.uuid', - ng-if='ctrl.roomShowState[room.uuid]', - on-dismiss='ctrl.onRoomDismiss(room)', - application='ctrl.application' - ) .sk-app-bar-item.border(ng-if="ctrl.state.showBetaWarning") .sk-app-bar-item(ng-if="ctrl.state.showBetaWarning") a.no-decoration.sk-label.title( @@ -89,21 +72,6 @@ ) .sk-app-bar-item(ng-if='ctrl.offline') .sk-label Offline - .sk-app-bar-item.border(ng-if='ctrl.state.dockShortcuts.length > 0') - .sk-app-bar-item.dock-shortcut(ng-repeat='shortcut in ctrl.state.dockShortcuts') - .sk-app-bar-item-column( - ng-class="{'underline': shortcut.component.active}", - ng-click='ctrl.selectShortcut(shortcut)' - ) - .div(ng-if="shortcut.icon.type == 'circle'" title='{{shortcut.name}}') - .sk-circle.small( - ng-style="{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}" - ) - .div(ng-if="shortcut.icon.type == 'svg'" title='{{shortcut.name}}') - .svg-item( - elem-ready='ctrl.initSvgForShortcut(shortcut)', - ng-attr-id='dock-svg-{{shortcut.component.uuid}}' - ) .sk-app-bar-item.border(ng-if='ctrl.state.hasAccountSwitcher') .sk-app-bar-item( ng-if='ctrl.state.hasAccountSwitcher' diff --git a/app/assets/javascripts/views/footer/footer_view.ts b/app/assets/javascripts/views/footer/footer_view.ts index c0064484c..f508e6485 100644 --- a/app/assets/javascripts/views/footer/footer_view.ts +++ b/app/assets/javascripts/views/footer/footer_view.ts @@ -5,7 +5,6 @@ import { preventRefreshing } from '@/utils'; import { ApplicationEvent, ContentType, - SNComponent, SNTheme, ComponentArea, CollectionSort, @@ -31,34 +30,21 @@ import { AccountMenuPane } from '@/components/AccountMenu'; const ACCOUNT_SWITCHER_ENABLED = false; const ACCOUNT_SWITCHER_FEATURE_KEY = 'account_switcher'; -type DockShortcut = { - name: string; - component: SNComponent; - icon: { - type: string; - background_color: string; - border_color: string; - }; -}; - class FooterViewCtrl extends PureViewCtrl< unknown, { outOfSync: boolean; hasPasscode: boolean; dataUpgradeAvailable: boolean; - dockShortcuts: DockShortcut[]; hasAccountSwitcher: boolean; showBetaWarning: boolean; showDataUpgrade: boolean; } > { private $rootScope: ng.IRootScopeService; - private rooms: SNComponent[] = []; private themesWithIcons: SNTheme[] = []; private showSyncResolution = false; private unregisterComponent: any; - private rootScopeListener1: any; private rootScopeListener2: any; public arbitraryStatusMessage?: string; public user?: any; @@ -66,12 +52,8 @@ class FooterViewCtrl extends PureViewCtrl< public showAccountMenu = false; public showQuickSettingsMenu = false; private didCheckForOffline = false; - private queueExtReload = false; - private reloadInProgress = false; public hasError = false; public newUpdateAvailable = false; - public dockShortcuts: DockShortcut[] = []; - public roomShowState: Partial> = {}; private observerRemovers: Array<() => void> = []; private completedInitialSync = false; private showingDownloadStatus = false; @@ -92,16 +74,13 @@ class FooterViewCtrl extends PureViewCtrl< deinit() { for (const remove of this.observerRemovers) remove(); this.observerRemovers.length = 0; - this.rooms.length = 0; this.themesWithIcons.length = 0; this.unregisterComponent(); this.unregisterComponent = undefined; - this.rootScopeListener1(); this.rootScopeListener2(); - this.rootScopeListener1 = undefined; this.rootScopeListener2 = undefined; - (this.closeAccountMenu as any) = undefined; - (this.toggleSyncResolutionMenu as any) = undefined; + (this.closeAccountMenu as unknown) = undefined; + (this.toggleSyncResolutionMenu as unknown) = undefined; super.deinit(); } @@ -141,7 +120,6 @@ class FooterViewCtrl extends PureViewCtrl< outOfSync: false, dataUpgradeAvailable: false, hasPasscode: false, - dockShortcuts: [], descriptors: this.mainApplicationGroup.getDescriptors(), hasAccountSwitcher: false, showBetaWarning: false, @@ -185,12 +163,6 @@ class FooterViewCtrl extends PureViewCtrl< } addRootScopeListeners() { - this.rootScopeListener1 = this.$rootScope.$on( - RootScopeMessages.ReloadExtendedData, - () => { - this.reloadExtendedData(); - } - ); this.rootScopeListener2 = this.$rootScope.$on( RootScopeMessages.NewUpdateAvailable, () => { @@ -207,7 +179,6 @@ class FooterViewCtrl extends PureViewCtrl< switch (eventName) { case AppStateEvent.EditorFocused: if (data.eventSource === EventSource.UserInteraction) { - this.closeAllRooms(); this.closeAccountMenu(); } break; @@ -303,37 +274,21 @@ class FooterViewCtrl extends PureViewCtrl< } ); - this.observerRemovers.push( - this.application.streamItems(ContentType.Component, async () => { - const components = this.application.getItems( - ContentType.Component - ) as SNComponent[]; - this.rooms = components.filter((candidate) => { - return candidate.area === ComponentArea.Rooms && !candidate.deleted; - }); - if (this.queueExtReload) { - this.queueExtReload = false; - this.reloadExtendedData(); - } - }) - ); - this.observerRemovers.push( this.application.streamItems(ContentType.Theme, async () => { const themes = this.application.getDisplayableItems( ContentType.Theme ) as SNTheme[]; this.themesWithIcons = themes; - this.reloadDockShortcuts(); }) ); } registerComponentHandler() { this.unregisterComponent = - this.application.componentManager!.registerHandler({ + this.application.componentManager.registerHandler({ identifier: 'room-bar', - areas: [ComponentArea.Rooms, ComponentArea.Modal], + areas: [ComponentArea.Modal], focusHandler: (component, focused) => { if (component.isEditor() && focused) { if ( @@ -342,7 +297,6 @@ class FooterViewCtrl extends PureViewCtrl< ) { return; } - this.closeAllRooms(); this.closeAccountMenu(); } }, @@ -351,7 +305,7 @@ class FooterViewCtrl extends PureViewCtrl< updateSyncStatus() { const statusManager = this.application.getStatusManager(); - const syncStatus = this.application!.getSyncStatus(); + const syncStatus = this.application.getSyncStatus(); const stats = syncStatus.getStats(); if (syncStatus.hasError()) { statusManager.setMessage('Unable to Sync'); @@ -385,9 +339,9 @@ class FooterViewCtrl extends PureViewCtrl< updateLocalDataStatus() { const statusManager = this.application.getStatusManager(); - const syncStatus = this.application!.getSyncStatus(); + const syncStatus = this.application.getSyncStatus(); const stats = syncStatus.getStats(); - const encryption = this.application!.isEncryptionAvailable(); + const encryption = this.application.isEncryptionAvailable(); if (stats.localDataDone) { statusManager.setMessage(''); return; @@ -399,35 +353,6 @@ class FooterViewCtrl extends PureViewCtrl< statusManager.setMessage(loadingStatus); } - reloadExtendedData() { - if (this.reloadInProgress) { - return; - } - this.reloadInProgress = true; - - /** - * A reload consists of opening the extensions manager, - * then closing it after a short delay. - */ - const extWindow = this.rooms.find((room) => { - return ( - room.package_info.identifier === - this.application.getNativeExtService().extManagerId - ); - }); - if (!extWindow) { - this.queueExtReload = true; - this.reloadInProgress = false; - return; - } - this.selectRoom(extWindow); - this.$timeout(() => { - this.selectRoom(extWindow); - this.reloadInProgress = false; - this.$rootScope.$broadcast('ext-reload-complete'); - }, 2000); - } - updateOfflineStatus() { this.offline = this.application.noAccount(); } @@ -453,7 +378,6 @@ class FooterViewCtrl extends PureViewCtrl< accountMenuPressed() { this.appState.quickSettingsMenu.closeQuickSettingsMenu(); this.appState.accountMenu.toggleShow(); - this.closeAllRooms(); } quickSettingsPressed() { @@ -463,7 +387,6 @@ class FooterViewCtrl extends PureViewCtrl< } else { this.appState.preferences.openPreferences(); } - this.closeAllRooms(); } toggleSyncResolutionMenu() { @@ -485,56 +408,7 @@ class FooterViewCtrl extends PureViewCtrl< clickedNewUpdateAnnouncement() { this.newUpdateAvailable = false; - this.application.alertService!.alert(STRING_NEW_UPDATE_READY); - } - - reloadDockShortcuts() { - const shortcuts: DockShortcut[] = []; - this.setState({ - dockShortcuts: shortcuts.sort((a, b) => { - /** Circles first, then images */ - const aType = a.icon.type; - const bType = b.icon.type; - if (aType === 'circle' && bType === 'svg') { - return -1; - } else if (bType === 'circle' && aType === 'svg') { - return 1; - } else { - return a.name.localeCompare(b.name); - } - }), - }); - } - - initSvgForShortcut(shortcut: DockShortcut) { - const id = 'dock-svg-' + shortcut.component.uuid; - const element = document.getElementById(id)!; - const parser = new DOMParser(); - const svg = shortcut.component.package_info.dock_icon?.source; - if (svg != undefined) { - const doc = parser.parseFromString(svg, 'image/svg+xml'); - element.appendChild(doc.documentElement); - } - } - - selectShortcut(shortcut: DockShortcut) { - this.application.toggleComponent(shortcut.component); - } - - onRoomDismiss(room: SNComponent) { - this.roomShowState[room.uuid] = false; - } - - closeAllRooms() { - for (const room of this.rooms) { - this.roomShowState[room.uuid] = false; - } - } - - async selectRoom(room: SNComponent) { - this.$timeout(() => { - this.roomShowState[room.uuid] = !this.roomShowState[room.uuid]; - }); + this.application.alertService.alert(STRING_NEW_UPDATE_READY); } displayBetaDialog() { diff --git a/app/extensions/extensions-manager b/app/extensions/extensions-manager deleted file mode 160000 index 81642c6e4..000000000 --- a/app/extensions/extensions-manager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 81642c6e4ee0ae8232732b8763e9bed91badce23 diff --git a/app/views/application/app.html.erb b/app/views/application/app.html.erb index c13c3c7d1..46d740df4 100644 --- a/app/views/application/app.html.erb +++ b/app/views/application/app.html.erb @@ -32,8 +32,6 @@ <% if Rails.env.development? %> diff --git a/index.html b/index.html index 649dbf8e0..34933df57 100644 --- a/index.html +++ b/index.html @@ -34,6 +34,7 @@ data-enable-unfinished-features="<%= env.ENABLE_UNFINISHED_FEATURES %>" data-web-socket-url="<%= env.DEV_WEBSOCKET_URL %>" data-purchase-url="<%= env.PURCHASE_URL %>" + data-dashboard-url="<%= env.DASHBOARD_URL %>" > From fd6d83655c54c74ba9ac9eeddd6f9fc27a3baa3d Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 27 Oct 2021 10:19:42 -0500 Subject: [PATCH 28/53] feat: Component toggleability and add toggleable components to quick settings menu (#707) * feat: toggleable extensions * fix: return all themes for quick settings * chore: bump snjs deps * feat: Use Switch component for toggle in Quick Settings Menu * feat: Add toggleableComponents to footer_view * refactor: Change "components" to "toggleableComponents" * feat: Add checked state to component toggle in quick settings menu --- .../components/QuickSettingsMenu.tsx | 55 +++++++++++++- .../preferences/PreferencesMenu.ts | 75 ++++++++++++------- .../preferences/components/Content.tsx | 4 + .../preferences/panes/Extensions.tsx | 2 +- .../extensions-segments/ExtensionItem.tsx | 27 +++---- .../ui_models/app_state/preferences_state.ts | 6 +- .../javascripts/views/footer/footer_view.ts | 28 +++++-- package.json | 4 +- yarn.lock | 8 +- 9 files changed, 151 insertions(+), 58 deletions(-) diff --git a/app/assets/javascripts/components/QuickSettingsMenu.tsx b/app/assets/javascripts/components/QuickSettingsMenu.tsx index b13a355b9..cf2ba591a 100644 --- a/app/assets/javascripts/components/QuickSettingsMenu.tsx +++ b/app/assets/javascripts/components/QuickSettingsMenu.tsx @@ -5,12 +5,18 @@ import { DisclosureButton, DisclosurePanel, } from '@reach/disclosure'; -import { ContentType, SNTheme } from '@standardnotes/snjs'; +import { + ContentType, + SNTheme, + ComponentArea, + SNComponent, +} from '@standardnotes/snjs'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { JSXInternal } from 'preact/src/jsx'; import { Icon } from './Icon'; +import { Switch } from './Switch'; import { toDirective, useCloseOnBlur } from './utils'; const MENU_CLASSNAME = @@ -75,6 +81,9 @@ const QuickSettingsMenu: FunctionComponent = observer( const { closeQuickSettingsMenu, shouldAnimateCloseMenu } = appState.quickSettingsMenu; const [themes, setThemes] = useState([]); + const [toggleableComponents, setToggleableComponents] = useState< + SNComponent[] + >([]); const [themesMenuOpen, setThemesMenuOpen] = useState(false); const [themesMenuPosition, setThemesMenuPosition] = useState({}); const [defaultThemeOn, setDefaultThemeOn] = useState(false); @@ -113,10 +122,29 @@ const QuickSettingsMenu: FunctionComponent = observer( }); }, [application]); + const reloadToggleableComponents = useCallback(() => { + application.streamItems(ContentType.Component, () => { + const toggleableComponents = ( + application.getDisplayableItems( + ContentType.Component + ) as SNComponent[] + ).filter((component) => + [ComponentArea.EditorStack, ComponentArea.TagsList].includes( + component.area + ) + ); + setToggleableComponents(toggleableComponents); + }); + }, [application]); + useEffect(() => { reloadThemes(); }, [reloadThemes]); + useEffect(() => { + reloadToggleableComponents(); + }, [reloadToggleableComponents]); + useEffect(() => { if (themesMenuOpen) { defaultThemeButtonRef.current!.focus(); @@ -127,7 +155,10 @@ const QuickSettingsMenu: FunctionComponent = observer( prefsButtonRef.current!.focus(); }, []); - const [closeOnBlur] = useCloseOnBlur(themesMenuRef as any, setThemesMenuOpen); + const [closeOnBlur] = useCloseOnBlur( + themesMenuRef as any, + setThemesMenuOpen + ); const toggleThemesMenu = () => { if (!themesMenuOpen) { @@ -149,6 +180,10 @@ const QuickSettingsMenu: FunctionComponent = observer( appState.preferences.openPreferences(); }; + const toggleComponent = (component: SNComponent) => { + application.toggleComponent(component); + }; + const handleBtnKeyDown: React.KeyboardEventHandler = ( event ) => { @@ -296,6 +331,22 @@ const QuickSettingsMenu: FunctionComponent = observer( ))}
    + + {toggleableComponents.map((component) => ( + { + toggleComponent(component); + }} + > +
    + + {component.name} +
    +
    + ))} +
    + ); }; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 8ac9088df..9deeee95e 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -422,6 +422,11 @@ padding-bottom: 0.625rem; } +.sn-component .px-0 { + padding-left: 0; + padding-right: 0; +} + .px-9 { padding-left: 2.25rem; padding-right: 2.25rem; From 4f56c453cb38faf51f080b98b29983c7439a5a7d Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Mon, 1 Nov 2021 19:49:06 +0530 Subject: [PATCH 33/53] feat: Update "Change Email" and "Change Password" modal designs (#714) * feat: Add new utility classes * feat: Update "Change Email" modal design * feat: Use .sk-input.contrast className to mimic password wizard inputs * feat: Add explicit labels to password wizard inputs * feat: Make button sizing consistent * refactor: Remove unused dependencies * refactor: Remove unused component --- .../components/shared/ModalDialog.tsx | 45 +++-- .../account/changeEmail/ChangeEmailForm.tsx | 64 +++--- .../panes/account/changeEmail/index.tsx | 17 +- .../changePassword/ChangePasswordForm.tsx | 45 ----- .../changePassword/ChangePasswordSuccess.tsx | 12 -- .../panes/account/changePassword/index.tsx | 189 ------------------ app/assets/stylesheets/_sn.scss | 10 + .../templates/directives/password-wizard.pug | 12 +- 8 files changed, 86 insertions(+), 308 deletions(-) delete mode 100644 app/assets/javascripts/preferences/panes/account/changePassword/ChangePasswordForm.tsx delete mode 100644 app/assets/javascripts/preferences/panes/account/changePassword/ChangePasswordSuccess.tsx delete mode 100644 app/assets/javascripts/preferences/panes/account/changePassword/index.tsx diff --git a/app/assets/javascripts/components/shared/ModalDialog.tsx b/app/assets/javascripts/components/shared/ModalDialog.tsx index d1888090a..e2a46a27a 100644 --- a/app/assets/javascripts/components/shared/ModalDialog.tsx +++ b/app/assets/javascripts/components/shared/ModalDialog.tsx @@ -5,7 +5,6 @@ import { AlertDialogLabel, } from '@node_modules/@reach/alert-dialog'; import { useRef } from '@node_modules/preact/hooks'; -import { IconButton } from '@/components/IconButton'; export const ModalDialog: FunctionComponent = ({ children }) => { const ldRef = useRef(null); @@ -19,7 +18,7 @@ export const ModalDialog: FunctionComponent = ({ children }) => {
    {children}
    @@ -30,17 +29,20 @@ export const ModalDialog: FunctionComponent = ({ children }) => { export const ModalDialogLabel: FunctionComponent<{ closeDialog: () => void; -}> = ({ children, closeDialog }) => ( - -
    -
    {children}
    - closeDialog()} - /> + className?: string; +}> = ({ children, closeDialog, className }) => ( + +
    +
    + {children} +
    +
    + Close +

    @@ -55,17 +57,20 @@ export const ModalDialogDescription: FunctionComponent<{ className?: string }> = ); -export const ModalDialogButtons: FunctionComponent = ({ children }) => ( +export const ModalDialogButtons: FunctionComponent<{ className?: string }> = ({ + children, + className, +}) => ( <>
    -
    +
    {children != undefined && Array.isArray(children) ? children.map((child, idx, arr) => ( - <> - {child} - {idx < arr.length - 1 ?
    : undefined} - - )) + <> + {child} + {idx < arr.length - 1 ?
    : undefined} + + )) : children}
    diff --git a/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx index c1f8ed7e3..9c6b8344a 100644 --- a/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx +++ b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx @@ -1,37 +1,47 @@ -import { DecoratedInput } from '@/components/DecoratedInput'; import { StateUpdater } from 'preact/hooks'; import { FunctionalComponent } from 'preact'; -import { HtmlInputTypes } from '@/enums'; type Props = { - setNewEmail: StateUpdater - setCurrentPassword: StateUpdater -} + setNewEmail: StateUpdater; + setCurrentPassword: StateUpdater; +}; + +const labelClassName = `block mb-1`; + +const inputClassName = 'sk-input contrast'; + export const ChangeEmailForm: FunctionalComponent = ({ setNewEmail, - setCurrentPassword + setCurrentPassword, }) => { return ( - ( - <> -
    - { - setNewEmail(newEmail); - }} - placeholder={'New Email'} - /> -
    -
    - { - setCurrentPassword(currentPassword); - }} - placeholder={'Current Password'} - /> -
    - - ) +
    +
    + + { + setNewEmail((target as HTMLInputElement).value); + }} + /> +
    +
    + + { + setCurrentPassword((target as HTMLInputElement).value); + }} + /> +
    +
    ); }; diff --git a/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx b/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx index 6569d2216..10e787053 100644 --- a/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx +++ b/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx @@ -154,10 +154,13 @@ export const ChangeEmail: FunctionalComponent = ({ return (
    - + Change Email - + {currentStep === Steps.InitialStep && ( = ({ )} {currentStep === Steps.FinishStep && } - - {currentStep === Steps.InitialStep && ( -
    - ); -}; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 9deeee95e..88f373760 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -427,6 +427,12 @@ padding-right: 0; } +.sn-component .px-4\.5, +.sn-component .sk-panel .px-4\.5 { + padding-left: 1.375rem; + padding-right: 1.375rem; +} + .px-9 { padding-left: 2.25rem; padding-right: 2.25rem; @@ -648,3 +654,7 @@ .list-style-none { list-style-type: none; } + +.rounded-0\.5 { + border-radius: 0.125rem; +} diff --git a/app/assets/templates/directives/password-wizard.pug b/app/assets/templates/directives/password-wizard.pug index 25dcb4a3d..9efa61e4f 100644 --- a/app/assets/templates/directives/password-wizard.pug +++ b/app/assets/templates/directives/password-wizard.pug @@ -12,24 +12,28 @@ .sk-panel-row .sk-panel-column.stretch form.sk-panel-form + label.block.mb-1(for='password-wiz-current-password') Current Password: input.sk-input.contrast( + id='password-wiz-current-password' ng-model='ctrl.state.formData.currentPassword', - placeholder='Current Password', should-focus='true', sn-autofocus='true', type='password' ) .sk-panel-row + label.block.mb-1(for='password-wiz-new-password') New Password: input.sk-input.contrast( + id='password-wiz-new-password', ng-if='ctrl.props.changePassword', ng-model='ctrl.state.formData.newPassword', - placeholder='New Password', type='password' ) + .sk-panel-row + label.block.mb-1(for='password-wiz-confirm-new-password') Confirm New Password: input.sk-input.contrast( + id='password-wiz-confirm-new-password', ng-if='ctrl.props.changePassword', ng-model='ctrl.state.formData.newPasswordConfirmation', - placeholder='Confirm New Password', type='password' ) .sk-panel-section(ng-if='ctrl.state.step == 2') @@ -41,7 +45,7 @@ | Please ensure you are running the latest version of Standard Notes | on all platforms to ensure maximum compatibility. .sk-panel-footer - button.sn-button.small.info( + button.sn-button.min-w-20.info( ng-click='ctrl.nextStep()', ng-disabled='ctrl.state.lockContinue' ) {{ctrl.state.continueTitle}} From 32ced95f728872cb83df951c0e9b4c05a5affdb0 Mon Sep 17 00:00:00 2001 From: Gorjan Petrovski Date: Mon, 1 Nov 2021 16:16:14 +0100 Subject: [PATCH 34/53] fix: replace border-gray-300 with a themable colour (#664) Co-authored-by: Aman Harwara --- app/assets/javascripts/components/Button.tsx | 4 ++-- app/assets/javascripts/components/DecoratedInput.tsx | 2 +- app/assets/javascripts/components/Input.tsx | 2 +- .../javascripts/components/shared/HorizontalSeparator.tsx | 2 +- app/assets/javascripts/components/shared/ModalDialog.tsx | 2 +- app/assets/javascripts/preferences/components/Content.tsx | 4 ++-- .../javascripts/preferences/components/PreferencesGroup.tsx | 2 +- app/assets/stylesheets/_preferences.scss | 2 +- app/assets/stylesheets/_sn.scss | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/components/Button.tsx b/app/assets/javascripts/components/Button.tsx index 484983dde..57a3c27fc 100644 --- a/app/assets/javascripts/components/Button.tsx +++ b/app/assets/javascripts/components/Button.tsx @@ -9,9 +9,9 @@ const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content`; type ButtonType = 'normal' | 'primary' | 'danger'; const buttonClasses: { [type in ButtonType]: string } = { - normal: `${baseClass} bg-default color-text border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`, + normal: `${baseClass} bg-default color-text border-solid border-neutral border-1 focus:bg-contrast hover:bg-contrast`, primary: `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 focus:brightness-130`, - danger: `${baseClass} bg-default color-danger border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`, + danger: `${baseClass} bg-default color-danger border-solid border-neutral border-1 focus:bg-contrast hover:bg-contrast`, }; export const Button: FunctionComponent<{ diff --git a/app/assets/javascripts/components/DecoratedInput.tsx b/app/assets/javascripts/components/DecoratedInput.tsx index fe3b67178..404ea153e 100644 --- a/app/assets/javascripts/components/DecoratedInput.tsx +++ b/app/assets/javascripts/components/DecoratedInput.tsx @@ -31,7 +31,7 @@ export const DecoratedInput: FunctionalComponent = ({ 'rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center bg-contrast'; const stateClasses = disabled ? 'no-border' - : 'border-solid border-1 border-gray-300'; + : 'border-solid border-1 border-neutral'; const classes = `${baseClasses} ${stateClasses} ${className}`; const inputBaseClasses = 'w-full no-border color-text focus:shadow-none bg-contrast'; diff --git a/app/assets/javascripts/components/Input.tsx b/app/assets/javascripts/components/Input.tsx index 39fac5e08..4d3ce5043 100644 --- a/app/assets/javascripts/components/Input.tsx +++ b/app/assets/javascripts/components/Input.tsx @@ -14,7 +14,7 @@ export const Input: FunctionalComponent = ({ const base = `rounded py-1.5 px-3 text-input my-1 h-8 bg-contrast`; const stateClasses = disabled ? 'no-border' - : 'border-solid border-1 border-gray-300'; + : 'border-solid border-1 border-neutral'; const classes = `${base} ${stateClasses} ${className}`; return ( diff --git a/app/assets/javascripts/components/shared/HorizontalSeparator.tsx b/app/assets/javascripts/components/shared/HorizontalSeparator.tsx index 7e107d75d..b570d672e 100644 --- a/app/assets/javascripts/components/shared/HorizontalSeparator.tsx +++ b/app/assets/javascripts/components/shared/HorizontalSeparator.tsx @@ -6,5 +6,5 @@ type Props = { export const HorizontalSeparator: FunctionalComponent = ({ classes = '' }) => { - return
    ; + return
    ; }; diff --git a/app/assets/javascripts/components/shared/ModalDialog.tsx b/app/assets/javascripts/components/shared/ModalDialog.tsx index e2a46a27a..8810e4faf 100644 --- a/app/assets/javascripts/components/shared/ModalDialog.tsx +++ b/app/assets/javascripts/components/shared/ModalDialog.tsx @@ -44,7 +44,7 @@ export const ModalDialogLabel: FunctionComponent<{ Close
    -
    +
    ); diff --git a/app/assets/javascripts/preferences/components/Content.tsx b/app/assets/javascripts/preferences/components/Content.tsx index 1d7e2009b..a3b896c9e 100644 --- a/app/assets/javascripts/preferences/components/Content.tsx +++ b/app/assets/javascripts/preferences/components/Content.tsx @@ -18,8 +18,8 @@ export const Text: FunctionComponent<{ className?: string }> = ({ }) =>

    {children}

    ; const buttonClasses = `block bg-default color-text rounded border-solid \ -border-1 border-gray-300 px-4 py-1.75 font-bold text-sm fit-content \ -focus:bg-contrast hover:bg-contrast`; +border-1 px-4 py-1.75 font-bold text-sm fit-content \ +focus:bg-contrast hover:bg-contrast border-neutral`; export const LinkButton: FunctionComponent<{ label: string; diff --git a/app/assets/javascripts/preferences/components/PreferencesGroup.tsx b/app/assets/javascripts/preferences/components/PreferencesGroup.tsx index 3d44f7c05..9010cedfb 100644 --- a/app/assets/javascripts/preferences/components/PreferencesGroup.tsx +++ b/app/assets/javascripts/preferences/components/PreferencesGroup.tsx @@ -7,7 +7,7 @@ const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({ }) => (index < length - 1 ? : null); export const PreferencesGroup: FunctionComponent = ({ children }) => ( -
    +
    {Array.isArray(children) ? children .filter( diff --git a/app/assets/stylesheets/_preferences.scss b/app/assets/stylesheets/_preferences.scss index 6bddccf02..9ccc4c669 100644 --- a/app/assets/stylesheets/_preferences.scss +++ b/app/assets/stylesheets/_preferences.scss @@ -26,7 +26,7 @@ } &:hover { - @extend .border-gray-300; + @extend .border-neutral; @extend .border-solid; @extend .border-1; @extend .bg-default; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 88f373760..fb1d648a1 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -203,7 +203,7 @@ @extend .border-bottom-solid; @extend .border-b-1; - @extend .border-gray-300; + @extend .border-neutral; @extend .py-3; @extend .px-3; From bad87a4f2f97c6d66c7d7ac5d239ba63f00d06e5 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 2 Nov 2021 19:55:28 +0530 Subject: [PATCH 35/53] feat: Replace border-gray-300 with border-neutral (#716) --- app/assets/javascripts/components/FloatingLabelInput.tsx | 2 +- app/assets/javascripts/components/NotesListOptionsMenu.tsx | 2 +- app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx | 2 +- app/assets/stylesheets/_menus.scss | 2 +- app/assets/stylesheets/_sn.scss | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/components/FloatingLabelInput.tsx b/app/assets/javascripts/components/FloatingLabelInput.tsx index 024c70656..4bde0c292 100644 --- a/app/assets/javascripts/components/FloatingLabelInput.tsx +++ b/app/assets/javascripts/components/FloatingLabelInput.tsx @@ -44,7 +44,7 @@ export const FloatingLabelInput: FunctionComponent = forwardRef( const INPUT_CLASSNAME = `w-full h-full ${ focused || value ? 'pt-6 pb-2' : 'py-2.5' - } px-3 text-input border-1 border-solid border-gray-300 rounded placeholder-medium text-input focus:ring-info ${ + } px-3 text-input border-1 border-solid border-neutral rounded placeholder-medium text-input focus:ring-info ${ isInvalid ? 'border-dark-red placeholder-dark-red' : '' } ${inputClassName}`; diff --git a/app/assets/javascripts/components/NotesListOptionsMenu.tsx b/app/assets/javascripts/components/NotesListOptionsMenu.tsx index 1d22e14f2..5c9ddc0e4 100644 --- a/app/assets/javascripts/components/NotesListOptionsMenu.tsx +++ b/app/assets/javascripts/components/NotesListOptionsMenu.tsx @@ -17,7 +17,7 @@ export const NotesListOptionsMenu: FunctionComponent = observer( ({ setShowMenuFalse, application }) => { const menuClassName = 'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \ -border-1 border-solid border-gray-300 text-sm z-index-dropdown-menu \ +border-1 border-solid border-neutral text-sm z-index-dropdown-menu \ flex flex-col py-2 bottom-0 left-2 absolute'; const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt) diff --git a/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx index a8539c51e..3ca2eb232 100644 --- a/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx +++ b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx @@ -36,7 +36,7 @@ export const PurchaseFlowView: FunctionComponent = return (
    -
    +
    Date: Wed, 3 Nov 2021 22:27:36 +0400 Subject: [PATCH 36/53] feat: fetch features and store locally for offline users (#706) * feat: fetch features and store locally for offline users * feat: handle success and error cases * refactor: move offline activation code reading/validation to snjs * chore: update after renaming snjs function * fix: correct condition for checking offline users * feat: let users remove their previous offline keys (WIP) * refactor: handle setOfflineFeatures function response accordingly * feat: remove corresponding local data when removing offline key * fix: use snjs' confirm dialog instead of custom one * feat: show warning before installing extension from untrusted source * refactor: move functions for validating external feature url and checking if custom server host was used to snjs * chore: put correct snjs version * chore: make `eslint-plugin-react-hooks` in yarn.lock to match the `develop` branch * chore: deps update * chore: deps update --- .../preferences/panes/AccountPreferences.tsx | 3 + .../preferences/panes/account/Advanced.tsx | 26 ++++ .../preferences/panes/account/index.ts | 1 + .../panes/account/offlineSubscription.tsx | 124 ++++++++++++++++++ app/assets/javascripts/strings.ts | 3 + package.json | 2 +- yarn.lock | 26 ++-- 7 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 app/assets/javascripts/preferences/panes/account/Advanced.tsx create mode 100644 app/assets/javascripts/preferences/panes/account/offlineSubscription.tsx diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx index faf149cbd..7c3db4272 100644 --- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -4,6 +4,7 @@ import { Credentials, SignOutWrapper, Authentication, + Advanced } from '@/preferences/panes/account'; import { PreferencesPane } from '@/preferences/components'; import { observer } from 'mobx-react-lite'; @@ -24,6 +25,7 @@ export const AccountPreferences = observer( {appState.enableUnfinishedFeatures && } + ); } @@ -34,6 +36,7 @@ export const AccountPreferences = observer( {appState.enableUnfinishedFeatures && } + ); } diff --git a/app/assets/javascripts/preferences/panes/account/Advanced.tsx b/app/assets/javascripts/preferences/panes/account/Advanced.tsx new file mode 100644 index 000000000..7b0193c33 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/Advanced.tsx @@ -0,0 +1,26 @@ +import { FunctionalComponent } from 'preact'; +import { PreferencesGroup, PreferencesSegment, Title } from '@/preferences/components'; +import { OfflineSubscription } from '@/preferences/panes/account/offlineSubscription'; +import { WebApplication } from '@/ui_models/application'; +import { observer } from 'mobx-react-lite'; +import { AppState } from '@/ui_models/app_state'; + +interface IProps { + application: WebApplication; + appState: AppState; +} + +export const Advanced: FunctionalComponent = observer(({ application, appState }) => { + return ( + + +
    +
    + Advanced Settings + +
    +
    +
    +
    + ); +}); diff --git a/app/assets/javascripts/preferences/panes/account/index.ts b/app/assets/javascripts/preferences/panes/account/index.ts index eb9c2bc51..85800edb5 100644 --- a/app/assets/javascripts/preferences/panes/account/index.ts +++ b/app/assets/javascripts/preferences/panes/account/index.ts @@ -3,3 +3,4 @@ export { Sync } from './Sync'; export { Credentials } from './Credentials'; export { SignOutWrapper } from './SignOutView'; export { Authentication } from './Authentication'; +export { Advanced } from './Advanced'; diff --git a/app/assets/javascripts/preferences/panes/account/offlineSubscription.tsx b/app/assets/javascripts/preferences/panes/account/offlineSubscription.tsx new file mode 100644 index 000000000..360aa40d0 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/offlineSubscription.tsx @@ -0,0 +1,124 @@ +import { FunctionalComponent } from 'preact'; +import { Subtitle } from '@/preferences/components'; +import { DecoratedInput } from '@/components/DecoratedInput'; +import { Button } from '@/components/Button'; +import { JSXInternal } from '@node_modules/preact/src/jsx'; +import TargetedEvent = JSXInternal.TargetedEvent; +import { useEffect, useState } from 'preact/hooks'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; +import { STRING_REMOVE_OFFLINE_KEY_CONFIRMATION } from '@/strings'; +import { ButtonType } from '@standardnotes/snjs'; + +interface IProps { + application: WebApplication; + appState: AppState; +} + +export const OfflineSubscription: FunctionalComponent = observer(({ application, appState }) => { + const [activationCode, setActivationCode] = useState(''); + const [isSuccessfullyActivated, setIsSuccessfullyActivated] = useState(false); + const [isSuccessfullyRemoved, setIsSuccessfullyRemoved] = useState(false); + const [hasUserPreviouslyStoredCode, setHasUserPreviouslyStoredCode] = useState(false); + + useEffect(() => { + if (application.getIsOfflineActivationCodeStoredPreviously()) { + setHasUserPreviouslyStoredCode(true); + } + }, [application]); + + const shouldShowOfflineSubscription = () => { + return !application.hasAccount() || application.isCustomServerHostUsed(); + }; + + const handleSubscriptionCodeSubmit = async (event: TargetedEvent) => { + event.preventDefault(); + + const result = await application.setOfflineFeatures(activationCode); + + if (result?.error) { + await application.alertService.alert(result.error); + } else { + setIsSuccessfullyActivated(true); + setHasUserPreviouslyStoredCode(true); + setIsSuccessfullyRemoved(false); + } + }; + + const handleRemoveOfflineKey = async () => { + await application.removeOfflineActivationCode(); + + setIsSuccessfullyActivated(false); + setHasUserPreviouslyStoredCode(false); + setActivationCode(''); + setIsSuccessfullyRemoved(true); + }; + + if (!shouldShowOfflineSubscription()) { + return null; + } + + const handleRemoveClick = async () => { + application.alertService.confirm( + STRING_REMOVE_OFFLINE_KEY_CONFIRMATION, + 'Remove offline key?', + 'Remove Offline Key', + ButtonType.Danger, + 'Cancel' + ) + .then(async (shouldRemove: boolean) => { + if (shouldRemove) { + await handleRemoveOfflineKey(); + } + }) + .catch((err: string) => { + application.alertService.alert(err); + }); + }; + + return ( +
    +
    + {!hasUserPreviouslyStoredCode && 'Activate'} Offline Subscription +
    +
    + {!hasUserPreviouslyStoredCode && ( + setActivationCode(code)} + placeholder={'Offline Subscription Code'} + text={activationCode} + disabled={isSuccessfullyActivated} + className={'mb-3'} + /> + )} +
    + {(isSuccessfullyActivated || isSuccessfullyRemoved) && ( +
    + Successfully {isSuccessfullyActivated ? 'Activated' : 'Removed'}! +
    + )} + {hasUserPreviouslyStoredCode && ( +
    +
    + ); +}); diff --git a/app/assets/javascripts/strings.ts b/app/assets/javascripts/strings.ts index dab4117e3..15d69e72c 100644 --- a/app/assets/javascripts/strings.ts +++ b/app/assets/javascripts/strings.ts @@ -111,6 +111,9 @@ export const STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT = 'Security Upgrade page.'; export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = 'Upgrade'; +export const STRING_REMOVE_OFFLINE_KEY_CONFIRMATION = + 'This will delete the previously saved offline key.'; + export const Strings = { protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.', diff --git a/package.json b/package.json index f41b2ffb3..b9174c599 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@reach/listbox": "^0.16.2", "@standardnotes/features": "1.8.0", "@standardnotes/sncrypto-web": "1.5.3", - "@standardnotes/snjs": "2.16.3", + "@standardnotes/snjs": "2.17.1", "mobx": "^6.3.5", "mobx-react-lite": "^3.2.1", "preact": "^10.5.15", diff --git a/yarn.lock b/yarn.lock index 24ed3414f..31f234a0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2166,10 +2166,10 @@ dependencies: "@standardnotes/auth" "^3.8.1" -"@standardnotes/features@1.7.3", "@standardnotes/features@^1.7.3": - version "1.7.3" - resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.7.3.tgz#4872c837fd11d069a8a41941bb3e5f294fb13d9c" - integrity sha512-G9NACv8pfVOB9O9L1C+Yoh25vMWVFLfF0FKSK5jjm/THm/w3SiQ2K82BIGgoQGpVGGAPEPa3Ru+OCBs3w8u+Jg== +"@standardnotes/features@1.8.0", "@standardnotes/features@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.8.0.tgz#1414350108714e376c57600b74437b9ae58cf343" + integrity sha512-gkBM82kEeKj4tve25WFsvPMb7MLqK2C3HcjKxk4RfoGouLAGjnSAVqs4AKZsDqdOrDZr5cAv55Uychr5qZMJTw== dependencies: "@standardnotes/common" "^1.2.1" @@ -2192,15 +2192,15 @@ buffer "^6.0.3" libsodium-wrappers "^0.7.9" -"@standardnotes/snjs@2.16.2": - version "2.16.2" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.16.2.tgz#2958ec0a2f1724343de82204f905311d7c3ffb65" - integrity sha512-G9HNu1TsAnK0OeRo6IYvmIR/huKoNkB+qWDPuh2+p/pJjLrtO6SGrOD4cm4Mg/63t29g8wW8Za/6/tPJHZOFCg== +"@standardnotes/snjs@2.17.1": + version "2.17.1" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.17.1.tgz#bb2761bd3f6f750c2497deb9ead8b85f3723ec1d" + integrity sha512-RPIZAG2cuxhwKhUA5bujw0Dzsb8k2xTrFRQtvTPdRAS+isqJid0nWjHyv3gfC8XsMmDp5J3Kurg+5sz77liQfQ== dependencies: "@standardnotes/auth" "^3.8.1" "@standardnotes/common" "^1.2.1" "@standardnotes/domain-events" "^2.5.1" - "@standardnotes/features" "^1.7.3" + "@standardnotes/features" "^1.8.0" "@standardnotes/settings" "^1.2.1" "@standardnotes/sncrypto-common" "^1.5.2" @@ -4006,10 +4006,10 @@ eslint-config-prettier@^8.3.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== -eslint-plugin-react-hooks@^4.2.1-alpha-4298ddbc5-20211023: - version "4.2.1-alpha-4298ddbc5-20211023" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.1-alpha-4298ddbc5-20211023.tgz#b227d6f900b4c86772585ae8130f2df625f05703" - integrity sha512-JK7BG4+zc4vpIMMxAiwgLQXQVrvHHKzrHmSNDFDwRwy4zVvKyp7mL9m8AsWvgFRQdWgmCeAACcrA4XqeNsxZ+w== +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== eslint-plugin-react@^7.26.1: version "7.26.1" From bbc81ea276e147bbed3d0cd5e9ceb6477e47c99f Mon Sep 17 00:00:00 2001 From: Vardan Hakobyan Date: Wed, 3 Nov 2021 22:45:50 +0400 Subject: [PATCH 37/53] feat: move extensions from prefs menu's left pane to General->Advanced section (#718) --- .../components/shared/AccordionItem.tsx | 44 ++++++++++ .../preferences/PreferencesMenu.ts | 2 - .../preferences/PreferencesView.tsx | 17 ++-- .../components/PreferencesSegment.tsx | 10 ++- .../preferences/panes/AccountPreferences.tsx | 2 - .../preferences/panes/Extensions.tsx | 87 +++++++++---------- .../javascripts/preferences/panes/General.tsx | 23 +++-- .../preferences/panes/account/Advanced.tsx | 38 +++++--- .../extensions-segments/ExtensionItem.tsx | 2 +- app/assets/stylesheets/_sn.scss | 25 ++++++ app/assets/stylesheets/_ui.scss | 3 + app/assets/svg/arrow-down.svg | 3 + 12 files changed, 179 insertions(+), 77 deletions(-) create mode 100644 app/assets/javascripts/components/shared/AccordionItem.tsx create mode 100644 app/assets/svg/arrow-down.svg diff --git a/app/assets/javascripts/components/shared/AccordionItem.tsx b/app/assets/javascripts/components/shared/AccordionItem.tsx new file mode 100644 index 000000000..ae87b2f31 --- /dev/null +++ b/app/assets/javascripts/components/shared/AccordionItem.tsx @@ -0,0 +1,44 @@ +import { FunctionalComponent } from 'preact'; +import { useRef, useState } from 'preact/hooks'; +import ArrowDown from '../../../svg/arrow-down.svg'; +import { Title } from '@/preferences/components'; + +type Props = { + title: string | JSX.Element; + className?: string; +} + +export const AccordionItem: FunctionalComponent = ({ + title, + className = '', + children + }) => { + const elementRef = useRef(null); + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
    +
    { + setIsExpanded(!isExpanded); + }} + > + {title} + +
    +
    + {children} +
    +
    + ); +}; diff --git a/app/assets/javascripts/preferences/PreferencesMenu.ts b/app/assets/javascripts/preferences/PreferencesMenu.ts index 702ff08d0..27e59dba1 100644 --- a/app/assets/javascripts/preferences/PreferencesMenu.ts +++ b/app/assets/javascripts/preferences/PreferencesMenu.ts @@ -11,7 +11,6 @@ const PREFERENCE_IDS = [ 'account', 'appearance', 'security', - 'extensions', 'listed', 'shortcuts', 'accessibility', @@ -38,7 +37,6 @@ const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [ { id: 'general', label: 'General', icon: 'settings' }, // { id: 'appearance', label: 'Appearance', icon: 'themes' }, { id: 'security', label: 'Security', icon: 'security' }, - { id: 'extensions', label: 'Extensions', icon: 'tune' }, { id: 'listed', label: 'Listed', icon: 'listed' }, // { id: 'shortcuts', label: 'Shortcuts', icon: 'keyboard' }, // { id: 'accessibility', label: 'Accessibility', icon: 'accessibility' }, diff --git a/app/assets/javascripts/preferences/PreferencesView.tsx b/app/assets/javascripts/preferences/PreferencesView.tsx index 3bdb64e1b..cd58f05a3 100644 --- a/app/assets/javascripts/preferences/PreferencesView.tsx +++ b/app/assets/javascripts/preferences/PreferencesView.tsx @@ -16,7 +16,6 @@ import { WebApplication } from '@/ui_models/application'; import { MfaProps } from './panes/two-factor-auth/MfaProps'; import { AppState } from '@/ui_models/app_state'; import { useEffect, useMemo } from 'preact/hooks'; -import { Extensions } from './panes/Extensions'; import { ExtensionPane } from './panes/ExtensionPane'; interface PreferencesProps extends MfaProps { @@ -38,7 +37,11 @@ const PaneSelector: FunctionComponent< switch (menu.selectedPaneId) { case 'general': return ( - + ); case 'account': return ( @@ -58,8 +61,6 @@ const PaneSelector: FunctionComponent< application={application} /> ); - case 'extensions': - return ; case 'listed': return ; case 'shortcuts': @@ -81,7 +82,13 @@ const PaneSelector: FunctionComponent< /> ); } else { - return ; + return ( + + ); } } }); diff --git a/app/assets/javascripts/preferences/components/PreferencesSegment.tsx b/app/assets/javascripts/preferences/components/PreferencesSegment.tsx index dad798f5c..4e12a79ce 100644 --- a/app/assets/javascripts/preferences/components/PreferencesSegment.tsx +++ b/app/assets/javascripts/preferences/components/PreferencesSegment.tsx @@ -1,5 +1,11 @@ import { FunctionComponent } from 'preact'; -export const PreferencesSegment: FunctionComponent = ({ children }) => ( -
    {children}
    +type Props = { + classes?: string; +} +export const PreferencesSegment: FunctionComponent = ({ + children, + classes = '' + }) => ( +
    {children}
    ); diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx index 7c3db4272..1b3259f08 100644 --- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -25,7 +25,6 @@ export const AccountPreferences = observer( {appState.enableUnfinishedFeatures && } - ); } @@ -36,7 +35,6 @@ export const AccountPreferences = observer( {appState.enableUnfinishedFeatures && } - ); } diff --git a/app/assets/javascripts/preferences/panes/Extensions.tsx b/app/assets/javascripts/preferences/panes/Extensions.tsx index 1da5d6aca..a92428bdf 100644 --- a/app/assets/javascripts/preferences/panes/Extensions.tsx +++ b/app/assets/javascripts/preferences/panes/Extensions.tsx @@ -5,8 +5,6 @@ import { WebApplication } from '@/ui_models/application'; import { FunctionComponent } from 'preact'; import { Title, - PreferencesGroup, - PreferencesPane, PreferencesSegment, } from '../components'; import { ConfirmCustomExtension, ExtensionItem, ExtensionsLatestVersions } from './extensions-segments'; @@ -70,55 +68,54 @@ export const Extensions: FunctionComponent<{ .filter(extension => !['modal', 'rooms'].includes(extension.area)); return ( - +
    {visibleExtensions.length > 0 && - - { - visibleExtensions - .sort((e1, e2) => e1.name.toLowerCase().localeCompare(e2.name.toLowerCase())) - .map((extension, i) => ( - - )) - } - +
    + { + visibleExtensions + .sort((e1, e2) => e1.name.toLowerCase().localeCompare(e2.name.toLowerCase())) + .map((extension, i) => ( + + )) + } +
    } - +
    {!confirmableExtension && - - Install Custom Extension -
    - { setCustomUrl(value); }} - /> -
    -
    +
    diff --git a/app/views/application/app.html.erb b/app/views/application/app.html.erb index 2911e7e4a..e3b893b77 100644 --- a/app/views/application/app.html.erb +++ b/app/views/application/app.html.erb @@ -36,6 +36,7 @@ window._enable_unfinished_features = "<%= ENV['ENABLE_UNFINISHED_FEATURES'] %>" === 'true'; window._websocket_url = "<%= ENV['WEBSOCKET_URL'] %>"; window._purchase_url = "<%= ENV['PURCHASE_URL'] %>"; + window._plans_url = "<%= ENV['PLANS_URL'] %>"; window._dashboard_url = "<%= ENV['DASHBOARD_URL'] %>"; diff --git a/index.html b/index.html index 07e6c45bc..3a8cb2968 100644 --- a/index.html +++ b/index.html @@ -34,6 +34,7 @@ data-enable-unfinished-features="<%= env.ENABLE_UNFINISHED_FEATURES %>" data-web-socket-url="<%= env.DEV_WEBSOCKET_URL %>" data-purchase-url="<%= env.PURCHASE_URL %>" + data-plans-url="<%= env.PLANS_URL %>" data-dashboard-url="<%= env.DASHBOARD_URL %>" > From 2fdb748babefb476207e664d03a1cc8b00ab562e Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 5 Nov 2021 20:43:27 +0530 Subject: [PATCH 41/53] fix: Change bg-neutral to bg-border (#723) --- .../components/shared/HorizontalSeparator.tsx | 10 +++++----- .../javascripts/components/shared/ModalDialog.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/components/shared/HorizontalSeparator.tsx b/app/assets/javascripts/components/shared/HorizontalSeparator.tsx index b570d672e..6eb89f123 100644 --- a/app/assets/javascripts/components/shared/HorizontalSeparator.tsx +++ b/app/assets/javascripts/components/shared/HorizontalSeparator.tsx @@ -2,9 +2,9 @@ import { FunctionalComponent } from 'preact'; type Props = { classes?: string; -} -export const HorizontalSeparator: FunctionalComponent = ({ - classes = '' -}) => { - return
    ; +}; +export const HorizontalSeparator: FunctionalComponent = ({ + classes = '', +}) => { + return
    ; }; diff --git a/app/assets/javascripts/components/shared/ModalDialog.tsx b/app/assets/javascripts/components/shared/ModalDialog.tsx index 8810e4faf..e2a46a27a 100644 --- a/app/assets/javascripts/components/shared/ModalDialog.tsx +++ b/app/assets/javascripts/components/shared/ModalDialog.tsx @@ -44,7 +44,7 @@ export const ModalDialogLabel: FunctionComponent<{ Close
    -
    +
    ); From 261bb65a85769c93ff95d613683c447549500dc2 Mon Sep 17 00:00:00 2001 From: Johnny A <5891646+johnny243@users.noreply.github.com> Date: Fri, 5 Nov 2021 11:31:23 -0400 Subject: [PATCH 42/53] fix: show alert when importing a zip file as a backup (#720) * fix: show alert when importing a zip file as a backup * Update app/assets/javascripts/strings.ts Co-authored-by: Mo Co-authored-by: Johnny Almonte Co-authored-by: Mo --- .../preferences/panes/security-segments/DataBackups.tsx | 6 ++++++ app/assets/javascripts/strings.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/app/assets/javascripts/preferences/panes/security-segments/DataBackups.tsx b/app/assets/javascripts/preferences/panes/security-segments/DataBackups.tsx index 13637dab5..9dec8bcbc 100644 --- a/app/assets/javascripts/preferences/panes/security-segments/DataBackups.tsx +++ b/app/assets/javascripts/preferences/panes/security-segments/DataBackups.tsx @@ -3,6 +3,7 @@ import { alertDialog } from '@Services/alertService'; import { STRING_IMPORT_SUCCESS, STRING_INVALID_IMPORT_FILE, + STRING_IMPORTING_ZIP_FILE, STRING_UNSUPPORTED_BACKUP_FILE_VERSION, StringImportError } from '@/strings'; @@ -36,6 +37,11 @@ export const DataBackups = observer(({ }; const readFile = async (file: File): Promise => { + if (file.type === 'application/zip') { + application.alertService.alert(STRING_IMPORTING_ZIP_FILE); + return; + } + return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { diff --git a/app/assets/javascripts/strings.ts b/app/assets/javascripts/strings.ts index 15d69e72c..1f6f647d8 100644 --- a/app/assets/javascripts/strings.ts +++ b/app/assets/javascripts/strings.ts @@ -80,6 +80,8 @@ export const STRING_GENERATING_LOGIN_KEYS = 'Generating Login Keys...'; export const STRING_GENERATING_REGISTER_KEYS = 'Generating Account Keys...'; export const STRING_INVALID_IMPORT_FILE = 'Unable to open file. Ensure it is a proper JSON file and try again.'; +export const STRING_IMPORTING_ZIP_FILE = + 'The file you selected is not a valid backup file. Please extract the contents of the zip file, then upload the contained .txt file.'; export function StringImportError(errorCount: number) { return `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`; } From 88cc50e8b6a1f88a679857ad456a341ad344f185 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sat, 6 Nov 2021 19:29:03 +0530 Subject: [PATCH 43/53] fix: Fix icon colors to be more themeable (#724) --- app/assets/icons/ic-accessibility.svg | 5 +++-- app/assets/icons/ic-chevron-down.svg | 6 +++--- app/assets/icons/ic-help.svg | 5 +++-- app/assets/icons/ic-info.svg | 5 +++-- app/assets/icons/ic-keyboard.svg | 5 +++-- app/assets/icons/ic-listed.svg | 6 ++++-- app/assets/icons/ic-security.svg | 5 +++-- app/assets/icons/ic-settings.svg | 5 +++-- app/assets/icons/ic-star.svg | 5 +++-- app/assets/icons/ic-user.svg | 5 +++-- .../components/AccountMenu/GeneralAccountMenu.tsx | 4 ++-- app/assets/stylesheets/_preferences.scss | 2 +- 12 files changed, 34 insertions(+), 24 deletions(-) diff --git a/app/assets/icons/ic-accessibility.svg b/app/assets/icons/ic-accessibility.svg index ffb780043..7d913389a 100644 --- a/app/assets/icons/ic-accessibility.svg +++ b/app/assets/icons/ic-accessibility.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-chevron-down.svg b/app/assets/icons/ic-chevron-down.svg index 1c89552e6..190774efb 100644 --- a/app/assets/icons/ic-chevron-down.svg +++ b/app/assets/icons/ic-chevron-down.svg @@ -1,4 +1,4 @@ - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/assets/icons/ic-help.svg b/app/assets/icons/ic-help.svg index eaed4c3f7..dfec7f52a 100644 --- a/app/assets/icons/ic-help.svg +++ b/app/assets/icons/ic-help.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-info.svg b/app/assets/icons/ic-info.svg index 14107de40..575677684 100644 --- a/app/assets/icons/ic-info.svg +++ b/app/assets/icons/ic-info.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-keyboard.svg b/app/assets/icons/ic-keyboard.svg index 8068326fd..3d37ab60a 100644 --- a/app/assets/icons/ic-keyboard.svg +++ b/app/assets/icons/ic-keyboard.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-listed.svg b/app/assets/icons/ic-listed.svg index 3bac23a5a..7179c323e 100644 --- a/app/assets/icons/ic-listed.svg +++ b/app/assets/icons/ic-listed.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-security.svg b/app/assets/icons/ic-security.svg index dfa4b37cc..06ab6033b 100644 --- a/app/assets/icons/ic-security.svg +++ b/app/assets/icons/ic-security.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-settings.svg b/app/assets/icons/ic-settings.svg index 2191bea9a..2d2a26528 100644 --- a/app/assets/icons/ic-settings.svg +++ b/app/assets/icons/ic-settings.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-star.svg b/app/assets/icons/ic-star.svg index f74b0d567..19072b969 100644 --- a/app/assets/icons/ic-star.svg +++ b/app/assets/icons/ic-star.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/icons/ic-user.svg b/app/assets/icons/ic-user.svg index 1bdfaf61f..ee303b0ee 100644 --- a/app/assets/icons/ic-user.svg +++ b/app/assets/icons/ic-user.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx index f2f6d0171..88fd1c718 100644 --- a/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx +++ b/app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx @@ -19,7 +19,7 @@ type Props = { closeMenu: () => void; }; -const iconClassName = 'color-grey-1 mr-2'; +const iconClassName = 'color-neutral mr-2'; export const GeneralAccountMenu: FunctionComponent = observer( ({ application, appState, setMenuPane, closeMenu }) => { @@ -60,7 +60,7 @@ export const GeneralAccountMenu: FunctionComponent = observer(
    Account
    - +
    {user ? ( diff --git a/app/assets/stylesheets/_preferences.scss b/app/assets/stylesheets/_preferences.scss index adea05605..5337a5d19 100644 --- a/app/assets/stylesheets/_preferences.scss +++ b/app/assets/stylesheets/_preferences.scss @@ -21,7 +21,7 @@ @extend .border-1; .icon { - color: var(--sn-stylekit-grey-1); + color: var(--sn-stylekit-neutral-color); @extend .text-base; } From f029e32dff0c59ed711006f629bb76ac3450426c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 9 Nov 2021 06:58:21 -0600 Subject: [PATCH 44/53] fix: site urls --- README.md | 4 ++-- app/assets/templates/directives/actions-menu.pug | 2 +- app/assets/templates/directives/editor-menu.pug | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e7512f18a..43f863768 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Standard Notes is a simple and private notes app available on most platforms, in - Simple and easy to use - Fast and encrypted cross-platform sync - Free sync on unlimited devices -- Extensible with editors (such as Markdown and Code), themes, and components (like Folders and Autocomplete Tags). Learn more about [Extended](https://standardnotes.com/extensions). +- Extensible with editors (such as Markdown and Code), themes, and components. [Learn more](https://standardnotes.com/features). - Open-source and the option to self-host your notes server. You can [host your own Standard Server](https://docs.standardnotes.com/self-hosting/getting-started) in a few easy steps. - A strong focus on longevity and sustainability. [Learn more](https://standardnotes.com/longevity). @@ -37,7 +37,7 @@ Standard Notes is a simple and private notes app available on most platforms, in ### Do More -If you're looking to power up your experience with extensions, and help support future development, [learn more about Extended](https://standardnotes.com/extensions). Extended offers: +If you're looking to power up your experience with extensions, and help support future development, [learn more about our paid plans](https://standardnotes.com/plans). Our paid plans offer: - Powerful editors, including the Plus Editor, Simple Markdown, Advanced Markdown, Code Editor, Vim Editor, and the popular Simple Task Editor. - Beautiful themes to help you find inspiration in any mood, like Midnight, Focused, Futura, Titanium, and Solarized Dark. diff --git a/app/assets/templates/directives/actions-menu.pug b/app/assets/templates/directives/actions-menu.pug index aa17f1839..ae6780316 100644 --- a/app/assets/templates/directives/actions-menu.pug +++ b/app/assets/templates/directives/actions-menu.pug @@ -1,7 +1,7 @@ .sn-component .sk-menu-panel.dropdown-menu a.no-decoration( - href='https://standardnotes.com/extensions', + href='https://standardnotes.com/plans', ng-if='self.state.extensions.length == 0', rel='noopener', target='blank' diff --git a/app/assets/templates/directives/editor-menu.pug b/app/assets/templates/directives/editor-menu.pug index 7d278faca..d537e81f3 100644 --- a/app/assets/templates/directives/editor-menu.pug +++ b/app/assets/templates/directives/editor-menu.pug @@ -22,7 +22,7 @@ ng-if='editor.conflictOf' ) Conflicted copy a.no-decoration( - href='https://standardnotes.com/extensions', + href='https://standardnotes.com/plans', ng-if='self.state.editors.length == 0', rel='noopener', target='blank' From 4b210832295044d78a502385ad06a3d12e431631 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 9 Nov 2021 16:41:12 -0600 Subject: [PATCH 45/53] fix: disable v4 feature checks --- .../javascripts/preferences/PreferencesMenu.ts | 8 ++++---- .../preferences/panes/AccountPreferences.tsx | 4 ++-- .../preferences/panes/Extensions.tsx | 4 +++- .../preferences/panes/HelpFeedback.tsx | 13 +++++++++---- .../preferences/panes/account/Credentials.tsx | 18 ++++++++---------- .../extensions-segments/ExtensionItem.tsx | 3 --- package.json | 6 +++--- yarn.lock | 18 +++++++++--------- 8 files changed, 38 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/preferences/PreferencesMenu.ts b/app/assets/javascripts/preferences/PreferencesMenu.ts index 27e59dba1..39115ea29 100644 --- a/app/assets/javascripts/preferences/PreferencesMenu.ts +++ b/app/assets/javascripts/preferences/PreferencesMenu.ts @@ -35,12 +35,12 @@ interface SelectableMenuItem extends PreferencesMenuItem { const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [ { id: 'account', label: 'Account', icon: 'user' }, { id: 'general', label: 'General', icon: 'settings' }, - // { id: 'appearance', label: 'Appearance', icon: 'themes' }, + { id: 'appearance', label: 'Appearance', icon: 'themes' }, { id: 'security', label: 'Security', icon: 'security' }, { id: 'listed', label: 'Listed', icon: 'listed' }, - // { id: 'shortcuts', label: 'Shortcuts', icon: 'keyboard' }, - // { id: 'accessibility', label: 'Accessibility', icon: 'accessibility' }, - // { id: 'get-free-month', label: 'Get a free month', icon: 'star' }, + { id: 'shortcuts', label: 'Shortcuts', icon: 'keyboard' }, + { id: 'accessibility', label: 'Accessibility', icon: 'accessibility' }, + { id: 'get-free-month', label: 'Get a free month', icon: 'star' }, { id: 'help-feedback', label: 'Help & feedback', icon: 'help' }, ]; diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx index 1b3259f08..265838bcd 100644 --- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -23,7 +23,7 @@ export const AccountPreferences = observer( return ( - {appState.enableUnfinishedFeatures && } + ); @@ -33,7 +33,7 @@ export const AccountPreferences = observer( - {appState.enableUnfinishedFeatures && } + ); diff --git a/app/assets/javascripts/preferences/panes/Extensions.tsx b/app/assets/javascripts/preferences/panes/Extensions.tsx index a92428bdf..5bf8b8f01 100644 --- a/app/assets/javascripts/preferences/panes/Extensions.tsx +++ b/app/assets/javascripts/preferences/panes/Extensions.tsx @@ -65,7 +65,9 @@ export const Extensions: FunctionComponent<{ }; const visibleExtensions = extensions - .filter(extension => !['modal', 'rooms'].includes(extension.area)); + .filter((extension) => { + return extension.package_info != undefined && !['modal', 'rooms'].includes(extension.area); + }); return (
    diff --git a/app/assets/javascripts/preferences/panes/HelpFeedback.tsx b/app/assets/javascripts/preferences/panes/HelpFeedback.tsx index d2a1d0d19..4fe16cf73 100644 --- a/app/assets/javascripts/preferences/panes/HelpFeedback.tsx +++ b/app/assets/javascripts/preferences/panes/HelpFeedback.tsx @@ -80,16 +80,21 @@ export const HelpAndFeedback: FunctionComponent = () => ( - Slack group + Community groups Want to meet other passionate note-takers and privacy enthusiasts? - Want to share your feedback with us? Join the Standard Notes Slack - group for discussions on security, themes, editors and more. + Want to share your feedback with us? Join the Standard Notes community + groups for discussions on security, themes, editors and more. + diff --git a/app/assets/javascripts/preferences/panes/account/Credentials.tsx b/app/assets/javascripts/preferences/panes/account/Credentials.tsx index 98613703c..2e0351898 100644 --- a/app/assets/javascripts/preferences/panes/account/Credentials.tsx +++ b/app/assets/javascripts/preferences/panes/account/Credentials.tsx @@ -33,16 +33,14 @@ export const Credentials: FunctionComponent = observer(({ application, ap You're signed in as {user?.email} - {appState.enableUnfinishedFeatures && ( -