diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000..952e0f64a --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,12 @@ +name: "Custom CodeQL Config" + +queries: +- uses: security-and-quality +- uses: ./.github/codeql/custom-queries/javascript + +paths: +- app/assets/javascripts + +paths-ignore: +- bin +- node_modules diff --git a/.github/codeql/custom-queries/javascript/qlpack.yml b/.github/codeql/custom-queries/javascript/qlpack.yml new file mode 100644 index 000000000..fcd24771d --- /dev/null +++ b/.github/codeql/custom-queries/javascript/qlpack.yml @@ -0,0 +1,4 @@ +name: custom-javascript-queries +version: 0.0.0 +libraryPathDependencies: +- codeql-javascript diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..0f8a2152a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,68 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop, main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '21 7 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # âšī¸ Command-line programs to run using the OS shell. + # đ https://git.io/JvXDl + + # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index 903e2617c..4656f67cc 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ dump.rdb # Yarn yarn-error.log package-lock.json + +codeqldb diff --git a/.npmignore b/.npmignore index bbeb12a69..2e0c1f200 100644 --- a/.npmignore +++ b/.npmignore @@ -37,3 +37,5 @@ package-lock.json package.json Rakefile testing-server.js +.github +codeqldb diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..44174c5f0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +.github +codeqldb diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 1db921aef..bf21ec96c 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -60,6 +60,7 @@ import { NoAccountWarningDirective } from './components/NoAccountWarning'; import { NoProtectionsdNoteWarningDirective } from './components/NoProtectionsNoteWarning'; import { SearchOptionsDirective } from './components/SearchOptions'; import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNotes'; +import { ConfirmSignoutDirective } from './components/ConfirmSignoutModal'; function reloadHiddenFirefoxTab(): boolean { /** @@ -149,7 +150,8 @@ const startApplication: StartApplication = async function startApplication( .directive('noAccountWarning', NoAccountWarningDirective) .directive('protectedNotePanel', NoProtectionsdNoteWarningDirective) .directive('searchOptions', SearchOptionsDirective) - .directive('multipleSelectedNotesPanel', MultipleSelectedNotesDirective); + .directive('multipleSelectedNotesPanel', MultipleSelectedNotesDirective) + .directive('confirmSignout', ConfirmSignoutDirective); // Filters angular.module('app').filter('trusted', ['$sce', trusted]); diff --git a/app/assets/javascripts/components/ConfirmSignoutModal.tsx b/app/assets/javascripts/components/ConfirmSignoutModal.tsx new file mode 100644 index 000000000..6d3cc19f2 --- /dev/null +++ b/app/assets/javascripts/components/ConfirmSignoutModal.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from 'preact/hooks'; +import { + AlertDialog, + AlertDialogDescription, + AlertDialogLabel, +} from '@reach/alert-dialog'; +import { STRING_SIGN_OUT_CONFIRMATION } from '@/strings'; +import { WebApplication } from '@/ui_models/application'; +import { toDirective } from './utils'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; + +type Props = { + application: WebApplication; + appState: AppState; +}; + +const ConfirmSignoutContainer = observer((props: Props) => { + if (!props.appState.accountMenu.signingOut) { + return null; + } + return ; +}); + +const ConfirmSignoutModal = observer(({ application, appState }: Props) => { + const [deleteLocalBackups, setDeleteLocalBackups] = useState( + application.hasAccount() + ); + + const cancelRef = useRef(); + function close() { + appState.accountMenu.setSigningOut(false); + } + + const [localBackupsCount, setLocalBackupsCount] = useState(0); + + useEffect(() => { + application.bridge.localBackupsCount().then(setLocalBackupsCount); + }, [appState.accountMenu.signingOut, application.bridge]); + + return ( + + + + + + + + End your session? + + + + {STRING_SIGN_OUT_CONFIRMATION} + + + {localBackupsCount > 0 && ( + + + + { + setDeleteLocalBackups( + (event.target as HTMLInputElement).checked + ); + }} + /> + + Delete {localBackupsCount} local backup file + {localBackupsCount > 1 ? 's' : ''} + + + { + application.bridge.viewlocalBackups(); + }} + > + View backup files + + + )} + + + Cancel + + { + if (deleteLocalBackups) { + application.signOutAndDeleteLocalBackups(); + } else { + application.signOut(); + } + close(); + }} + > + {application.hasAccount() + ? 'Sign Out' + : 'Clear Session Data'} + + + + + + + + + ); +}); + +export const ConfirmSignoutDirective = toDirective( + ConfirmSignoutContainer +); diff --git a/app/assets/javascripts/directives/views/accountMenu.ts b/app/assets/javascripts/directives/views/accountMenu.ts index 7ea4b2438..1772e7975 100644 --- a/app/assets/javascripts/directives/views/accountMenu.ts +++ b/app/assets/javascripts/directives/views/accountMenu.ts @@ -84,6 +84,8 @@ class AccountMenuCtrl extends PureViewCtrl { private closeFunction?: () => void; private removeProtectionLengthObserver?: () => void; + public passcodeInput!: JQLite; + /* @ngInject */ constructor($timeout: ng.ITimeoutService, appVersion: string) { super($timeout); @@ -144,7 +146,7 @@ class AccountMenuCtrl extends PureViewCtrl { async $onInit() { super.$onInit(); this.setState({ - showSessions: await this.application.userCanManageSessions() + showSessions: await this.application.userCanManageSessions(), }); const sync = this.appState.sync; @@ -379,15 +381,8 @@ class AccountMenuCtrl extends PureViewCtrl { this.appState.openSessionsModal(); } - async destroyLocalData() { - if ( - await confirmDialog({ - text: STRING_SIGN_OUT_CONFIRMATION, - confirmButtonStyle: 'danger', - }) - ) { - this.application.signOut(); - } + signOut() { + this.appState.accountMenu.setSigningOut(true); } showRegister() { @@ -517,17 +512,21 @@ class AccountMenuCtrl extends PureViewCtrl { async submitPasscodeForm() { const passcode = this.getState().formData.passcode!; if (passcode !== this.getState().formData.confirmPasscode!) { - this.application!.alertService!.alert(STRING_NON_MATCHING_PASSCODES); + await alertDialog({ + text: STRING_NON_MATCHING_PASSCODES, + }); + this.passcodeInput[0].focus(); return; } await preventRefreshing( STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE, async () => { - if (this.application!.hasPasscode()) { - await this.application!.changePasscode(passcode); - } else { - await this.application!.addPasscode(passcode); + const successful = this.application.hasPasscode() + ? await this.application.changePasscode(passcode) + : await this.application.addPasscode(passcode); + if (!successful) { + this.passcodeInput[0].focus(); } } ); diff --git a/app/assets/javascripts/directives/views/panelResizer.ts b/app/assets/javascripts/directives/views/panelResizer.ts index 67307e618..c39f40743 100644 --- a/app/assets/javascripts/directives/views/panelResizer.ts +++ b/app/assets/javascripts/directives/views/panelResizer.ts @@ -313,9 +313,7 @@ class PanelResizerCtrl implements PanelResizerScope { } if (Math.round(width + this.lastLeft) === Math.round(parentRect.width)) { this.panel.style.width = `calc(100% - ${this.lastLeft}px)`; - this.panel.style.flexBasis = `calc(100% - ${this.lastLeft}px)`; } else { - this.panel.style.flexBasis = width + 'px'; this.panel.style.width = width + 'px'; } this.lastWidth = width; @@ -344,8 +342,8 @@ class PanelResizerCtrl implements PanelResizerScope { /** * If an iframe is displayed adjacent to our panel, and the mouse exits over the iframe, - * document[onmouseup] is not triggered because the document is no longer the same over - * the iframe. We add an invisible overlay while resizing so that the mouse context + * document[onmouseup] is not triggered because the document is no longer the same over + * the iframe. We add an invisible overlay while resizing so that the mouse context * remains in our main document. */ addInvisibleOverlay() { diff --git a/app/assets/javascripts/services/bridge.ts b/app/assets/javascripts/services/bridge.ts index 5cf08d83a..6e9c74854 100644 --- a/app/assets/javascripts/services/bridge.ts +++ b/app/assets/javascripts/services/bridge.ts @@ -10,9 +10,13 @@ export interface Bridge { environment: Environment; getKeychainValue(): Promise; - setKeychainValue(value: any): Promise; + setKeychainValue(value: unknown): Promise; clearKeychainValue(): Promise; + localBackupsCount(): Promise; + viewlocalBackups(): void; + deleteLocalBackups(): Promise; + extensionsServerHost?: string; syncComponents(payloads: unknown[]): void; onMajorDataChange(): void; diff --git a/app/assets/javascripts/services/browserBridge.ts b/app/assets/javascripts/services/browserBridge.ts index 29796ec80..174b933bb 100644 --- a/app/assets/javascripts/services/browserBridge.ts +++ b/app/assets/javascripts/services/browserBridge.ts @@ -1,4 +1,4 @@ -import { Bridge } from "./bridge"; +import { Bridge } from './bridge'; import { Environment } from '@standardnotes/snjs'; const KEYCHAIN_STORAGE_KEY = 'keychain'; @@ -14,7 +14,7 @@ export class BrowserBridge implements Bridge { } } - async setKeychainValue(value: any): Promise { + async setKeychainValue(value: unknown): Promise { localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(value)); } @@ -22,9 +22,16 @@ export class BrowserBridge implements Bridge { localStorage.removeItem(KEYCHAIN_STORAGE_KEY); } + async localBackupsCount(): Promise { + /** Browsers cannot save backups, only let you download one */ + return 0; + } + /** No-ops */ /* eslint-disable @typescript-eslint/no-empty-function */ + async deleteLocalBackups(): Promise {} + viewlocalBackups(): void {} syncComponents(): void {} onMajorDataChange(): void {} onInitialDataLoad(): void {} diff --git a/app/assets/javascripts/strings.ts b/app/assets/javascripts/strings.ts index 19a9f6f1d..21d00ee01 100644 --- a/app/assets/javascripts/strings.ts +++ b/app/assets/javascripts/strings.ts @@ -40,6 +40,8 @@ export const STRING_UNARCHIVE_LOCKED_ATTEMPT = "This note is locked. If you'd like to archive it, unlock it, and try again."; export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again."; +export const STRING_EDIT_LOCKED_ATTEMPT = + "This note is locked. If you'd like to edit its options, unlock it, and try again."; export function StringDeleteNote(title: string, permanently: boolean) { return permanently ? `Are you sure you want to permanently delete ${title}?` @@ -52,8 +54,7 @@ export function StringEmptyTrash(count: number) { /** @account */ export const STRING_ACCOUNT_MENU_UNCHECK_MERGE = 'Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?'; -export const STRING_SIGN_OUT_CONFIRMATION = - 'Are you sure you want to end your session? This will delete all local items and extensions.'; +export const STRING_SIGN_OUT_CONFIRMATION = 'This will delete all local items and extensions.'; export const STRING_ERROR_DECRYPTING_IMPORT = 'There was an error decrypting your items. Make sure the password you entered is correct and try again.'; export const STRING_E2E_ENABLED = 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 bf7489495..2ef9eb719 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 @@ -2,12 +2,16 @@ import { action, makeObservable, observable } from "mobx"; export class AccountMenuState { show = false; + signingOut = false; constructor() { makeObservable(this, { show: observable, + signingOut: observable, + setShow: action, toggleShow: action, + setSigningOut: action, }); } @@ -15,6 +19,10 @@ export class AccountMenuState { this.show = show; } + setSigningOut = (signingOut: boolean): void => { + this.signingOut = signingOut; + } + toggleShow = (): void => { this.show = !this.show; } diff --git a/app/assets/javascripts/ui_models/app_state/search_options_state.ts b/app/assets/javascripts/ui_models/app_state/search_options_state.ts index 7be4be6d2..9b9b4aa50 100644 --- a/app/assets/javascripts/ui_models/app_state/search_options_state.ts +++ b/app/assets/javascripts/ui_models/app_state/search_options_state.ts @@ -4,7 +4,7 @@ import { WebApplication } from "../application"; export class SearchOptionsState { includeProtectedContents = false; - includeArchived = false; + includeArchived = true; includeTrashed = false; constructor( diff --git a/app/assets/javascripts/ui_models/application.ts b/app/assets/javascripts/ui_models/application.ts index 95c729049..3a545963b 100644 --- a/app/assets/javascripts/ui_models/application.ts +++ b/app/assets/javascripts/ui_models/application.ts @@ -52,7 +52,7 @@ export class WebApplication extends SNApplication { private $compile: angular.ICompileService, scope: angular.IScope, defaultSyncServerHost: string, - private bridge: Bridge, + public bridge: Bridge, ) { super( bridge.environment, @@ -168,6 +168,11 @@ export class WebApplication extends SNApplication { return angular.element(document.getElementById(this.identifier)!); } + async signOutAndDeleteLocalBackups(): Promise { + await this.bridge.deleteLocalBackups(); + return this.signOut(); + } + presentPasswordModal(callback: () => void) { const scope = this.scope!.$new(true) as InputModalScope; scope.type = "password"; diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index f5884c803..9f484223e 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -34,6 +34,7 @@ import { STRING_ELLIPSES, STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, + STRING_EDIT_LOCKED_ATTEMPT, StringDeleteNote, StringEmptyTrash, } from '@/strings'; @@ -88,11 +89,15 @@ type EditorState = { }; type EditorValues = { - title?: string; - text?: string; + title: string; + text: string; tagsInputValue?: string; }; +function copyEditorValues(values: EditorValues) { + return Object.assign({}, values); +} + function sortAlphabetically(array: SNComponent[]): SNComponent[] { return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1 @@ -110,7 +115,7 @@ class EditorViewCtrl extends PureViewCtrl { private saveTimeout?: ng.IPromise; private statusTimeout?: ng.IPromise; private lastEditorFocusEventSource?: EventSource; - public editorValues: EditorValues = {}; + public editorValues: EditorValues = { title: '', text: '' }; onEditorLoad?: () => void; private tags: SNTag[] = []; @@ -198,9 +203,12 @@ class EditorViewCtrl extends PureViewCtrl { if (!this.editorValues.text) { this.editorValues.text = note.text; } - if (note.lastSyncBegan) { + if (note.lastSyncBegan || note.dirty) { if (note.lastSyncEnd) { - if (note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()) { + if ( + note.dirty || + note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime() + ) { this.showSavingStatus(); } else if ( note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime() @@ -301,6 +309,9 @@ class EditorViewCtrl extends PureViewCtrl { this.reloadPreferences(); this.reloadStackComponents(); this.reloadNoteTagsComponent(); + if (note.dirty) { + this.showSavingStatus(); + } if (note.safeText().length === 0 && !showProtectedWarning) { this.focusTitle(); } @@ -408,6 +419,10 @@ class EditorViewCtrl extends PureViewCtrl { if (this.appState.getActiveEditor()?.isTemplateNote) { await this.appState.getActiveEditor().insertTemplatedNote(); } + if (this.note.locked) { + this.application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT); + return; + } if (!component) { if (!this.note.prefersPlainEditor) { await this.application.changeItem(this.note.uuid, (mutator) => { @@ -464,26 +479,30 @@ class EditorViewCtrl extends PureViewCtrl { * close the editor (if we closed the editor before sync began, we'd get an exception, * since the debouncer will be triggered on a non-existent editor) */ - async saveNote( + async save( + note: SNNote, + editorValues: EditorValues, bypassDebouncer = false, isUserModified = false, dontUpdatePreviews = false, customMutate?: (mutator: NoteMutator) => void, closeAfterSync = false ) { + const title = editorValues.title; + const text = editorValues.text; + const isTemplate = this.editor.isTemplateNote; + const selectedTag = this.appState.selectedTag; if (document.hidden) { - this.application.alertService!.alert(STRING_SAVING_WHILE_DOCUMENT_HIDDEN); + this.application.alertService.alert(STRING_SAVING_WHILE_DOCUMENT_HIDDEN); return; } - const note = this.note; if (note.deleted) { - this.application.alertService!.alert(STRING_DELETED_NOTE); + this.application.alertService.alert(STRING_DELETED_NOTE); return; } - if (this.editor.isTemplateNote) { + if (isTemplate) { await this.editor.insertTemplatedNote(); } - const selectedTag = this.appState.selectedTag; if ( !selectedTag?.isSmartTag && !selectedTag?.hasRelationshipWithItem(note) @@ -493,7 +512,7 @@ class EditorViewCtrl extends PureViewCtrl { }); } if (!this.application.findItem(note.uuid)) { - this.application.alertService!.alert(STRING_INVALID_NOTE); + this.application.alertService.alert(STRING_INVALID_NOTE); return; } await this.application.changeItem( @@ -503,12 +522,12 @@ class EditorViewCtrl extends PureViewCtrl { if (customMutate) { customMutate(noteMutator); } - noteMutator.title = this.editorValues.title!; - noteMutator.text = this.editorValues.text!; + noteMutator.title = title; + noteMutator.text = text; if (!dontUpdatePreviews) { - const text = this.editorValues.text || ''; - const truncate = text.length > NOTE_PREVIEW_CHAR_LIMIT; - const substring = text.substring(0, NOTE_PREVIEW_CHAR_LIMIT); + const noteText = text || ''; + const truncate = noteText.length > NOTE_PREVIEW_CHAR_LIMIT; + const substring = noteText.substring(0, NOTE_PREVIEW_CHAR_LIMIT); const previewPlain = substring + (truncate ? STRING_ELLIPSES : ''); noteMutator.preview_plain = previewPlain; noteMutator.preview_html = undefined; @@ -541,7 +560,8 @@ class EditorViewCtrl extends PureViewCtrl { syncTakingTooLong: false, }); this.setStatus({ - message: 'All changes saved' + (this.application.noAccount() ? ' offline' : ''), + message: + 'All changes saved' + (this.application.noAccount() ? ' offline' : ''), }); } @@ -583,7 +603,7 @@ class EditorViewCtrl extends PureViewCtrl { } contentChanged() { - this.saveNote(false, true); + this.save(this.note, copyEditorValues(this.editorValues), false, true); } onTitleEnter($event: Event) { @@ -593,7 +613,13 @@ class EditorViewCtrl extends PureViewCtrl { } onTitleChange() { - this.saveNote(false, true, true); + this.save( + this.note, + copyEditorValues(this.editorValues), + false, + true, + true + ); } focusEditor() { @@ -653,9 +679,16 @@ class EditorViewCtrl extends PureViewCtrl { if (permanently) { this.performNoteDeletion(this.note); } else { - this.saveNote(true, false, true, (mutator) => { - mutator.trashed = true; - }); + this.save( + this.note, + copyEditorValues(this.editorValues), + true, + false, + true, + (mutator) => { + mutator.trashed = true; + } + ); } } } @@ -665,7 +698,9 @@ class EditorViewCtrl extends PureViewCtrl { } restoreTrashedNote() { - this.saveNote( + this.save( + this.note, + copyEditorValues(this.editorValues), true, false, true, @@ -698,15 +733,31 @@ class EditorViewCtrl extends PureViewCtrl { } togglePin() { - this.saveNote(true, false, true, (mutator) => { - mutator.pinned = !this.note.pinned; - }); + const note = this.note; + this.save( + note, + copyEditorValues(this.editorValues), + true, + false, + true, + (mutator) => { + mutator.pinned = !note.pinned; + } + ); } toggleLockNote() { - this.saveNote(true, false, true, (mutator) => { - mutator.locked = !this.note.locked; - }); + const note = this.note; + this.save( + note, + copyEditorValues(this.editorValues), + true, + false, + true, + (mutator) => { + mutator.locked = !note.locked; + } + ); } async toggleProtectNote() { @@ -723,29 +774,40 @@ class EditorViewCtrl extends PureViewCtrl { } toggleNotePreview() { - this.saveNote(true, false, true, (mutator) => { - mutator.hidePreview = !this.note.hidePreview; - }); + const note = this.note; + this.save( + note, + copyEditorValues(this.editorValues), + true, + false, + true, + (mutator) => { + mutator.hidePreview = !note.hidePreview; + } + ); } toggleArchiveNote() { - if (this.note.locked) { + const note = this.note; + if (note.locked) { alertDialog({ - text: this.note.archived + text: note.archived ? STRING_UNARCHIVE_LOCKED_ATTEMPT : STRING_ARCHIVE_LOCKED_ATTEMPT, }); return; } - this.saveNote( + this.save( + note, + copyEditorValues(this.editorValues), true, false, true, (mutator) => { - mutator.archived = !this.note.archived; + mutator.archived = !note.archived; }, /** If we are unarchiving, and we are in the archived tag, close the editor */ - this.note.archived && this.appState.selectedTag?.isArchiveTag + note.archived && this.appState.selectedTag?.isArchiveTag ); } @@ -1176,7 +1238,7 @@ class EditorViewCtrl extends PureViewCtrl { editor.selectionStart = editor.selectionEnd = start + 4; } this.editorValues.text = editor.value; - this.saveNote(true); + this.save(this.note, copyEditorValues(this.editorValues), true); }, }); diff --git a/app/assets/javascripts/views/notes/notes-view.pug b/app/assets/javascripts/views/notes/notes-view.pug index ce7c84849..1240ec740 100644 --- a/app/assets/javascripts/views/notes/notes-view.pug +++ b/app/assets/javascripts/views/notes/notes-view.pug @@ -7,7 +7,8 @@ .sk-button.contrast.wide( ng-click='self.createNewNote()', title='Create a new note in the selected tag' - ) + aria-label="Create new note" + ) .sk-label i.icon.ion-plus.add-button .filter-section(role='search') diff --git a/app/assets/stylesheets/_main.scss b/app/assets/stylesheets/_main.scss index 06c659c4e..46803073c 100644 --- a/app/assets/stylesheets/_main.scss +++ b/app/assets/stylesheets/_main.scss @@ -27,6 +27,7 @@ body { min-height: 100%; height: 100%; font-size: var(--sn-stylekit-base-font-size); + line-height: normal; margin: 0; color: var(--sn-stylekit-foreground-color); background-color: var(--sn-stylekit-background-color); diff --git a/app/assets/stylesheets/_notes.scss b/app/assets/stylesheets/_notes.scss index 14ced6ee2..aa7a15c44 100644 --- a/app/assets/stylesheets/_notes.scss +++ b/app/assets/stylesheets/_notes.scss @@ -83,7 +83,7 @@ position: absolute; top: 50%; transform: translateY(-50%); - right: 32px; + right: 36px; cursor: pointer; transition: background-color 0.15s linear; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index b3b1a70b5..d42ff0147 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -49,6 +49,10 @@ outline: none; } +.color-foreground { + color: var(--sn-stylekit-foreground-color); +} + .ring-info { box-shadow: 0 0 0 2px var(--sn-stylekit-info-color); } diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug index 97f0a49d8..d2283ccfb 100644 --- a/app/assets/templates/directives/account-menu.pug +++ b/app/assets/templates/directives/account-menu.pug @@ -30,7 +30,7 @@ ) .sk-panel-section-title | {{self.state.formData.showLogin ? "Sign In" : "Register"}} - form.sk-panel-form(ng-submit='self.submitAuthForm()') + form.sk-panel-form(ng-submit='self.submitAuthForm()' novalidate) .sk-panel-section input.sk-input.contrast( name='email', @@ -200,12 +200,13 @@ ) .sk-panel-row input.sk-input.contrast( - ng-model='self.state.formData.passcode', - placeholder='Passcode', - should-focus='true', - sn-autofocus='true', + ng-ref='self.passcodeInput' + ng-model='self.state.formData.passcode' + placeholder='Passcode' + should-focus='true' + sn-autofocus='true' type='password' - ) + ) input.sk-input.contrast( ng-model='self.state.formData.confirmPasscode', placeholder='Confirm Passcode', @@ -297,6 +298,10 @@ | {{ self.state.errorReportingEnabled ? 'Disable' : 'Enable'}} Error Reporting .sk-panel-row a(ng-click="self.openErrorReportingDialog()").sk-a What data is being sent? + confirm-signout( + app-state='self.appState' + application='self.application' + ) .sk-panel-footer .sk-panel-row .sk-p.left.neutral @@ -311,7 +316,7 @@ ) | Cancel a.sk-a.right.danger.capitalize( - ng-click='self.destroyLocalData()', + ng-click='self.signOut()', ng-if=` !self.state.formData.showLogin && !self.state.formData.showRegister` diff --git a/package.json b/package.json index 51a9325e4..213436253 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "standard-notes-web", - "version": "3.6.6", + "version": "3.6.8", "license": "AGPL-3.0-or-later", "repository": { "type": "git", @@ -55,7 +55,7 @@ "pug-loader": "^2.4.0", "sass-loader": "^8.0.2", "serve-static": "^1.14.1", - "sn-stylekit": "github:standardnotes/StyleKit#c64573b229ac9154e480cde3b5fdab78c75b3530", + "sn-stylekit": "^4.0.3", "ts-loader": "^8.0.17", "typescript": "^4.1.5", "typescript-eslint": "0.0.1-alpha.0", @@ -71,7 +71,7 @@ "@reach/checkbox": "^0.13.2", "@reach/dialog": "^0.13.0", "@standardnotes/sncrypto-web": "^1.2.10", - "@standardnotes/snjs": "^2.0.74", + "@standardnotes/snjs": "^2.0.75", "mobx": "^6.1.6", "mobx-react-lite": "^3.2.0", "preact": "^10.5.12" diff --git a/yarn.lock b/yarn.lock index 696d2be26..508d97e0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1936,10 +1936,10 @@ "@standardnotes/sncrypto-common" "^1.2.7" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@^2.0.74": - version "2.0.74" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.74.tgz#042c3dcf5447006cdfc70b8cfcacf7ded92cea91" - integrity sha512-2G0jw1n4GgOnSkpXDXvrJp9R8xsImVrfNRjKNBE7RqzBg2WFynF5xt0bfqzLdRFZyA5oeuEkTG1CAU4nNPy0xw== +"@standardnotes/snjs@^2.0.75": + version "2.0.75" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.75.tgz#aeb0ead927da63dc85e28f78da2362126bb16602" + integrity sha512-QL5YgDT0aN9t95gxgURqNudXr5dteVsc1ylsKKSw0DpEGiq0bACPxbI+sUFppoWTFmprxmDh3+vc+FFcFg7Lyw== dependencies: "@standardnotes/auth" "^2.0.0" "@standardnotes/sncrypto-common" "^1.2.9" @@ -7815,9 +7815,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -"sn-stylekit@github:standardnotes/StyleKit#c64573b229ac9154e480cde3b5fdab78c75b3530": - version "4.0.2" - resolved "https://codeload.github.com/standardnotes/StyleKit/tar.gz/c64573b229ac9154e480cde3b5fdab78c75b3530" +sn-stylekit@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-4.0.3.tgz#f8b86a68286bf5237dfc035c43a31464625b010c" + integrity sha512-cKsq3XndExpzJnP6qjd5khGbNhKlA724MLbxbMbgUv3aYQWZddHudzV84Lb5gx87mbUQJQc0lh0nPWZBpa2hUA== snapdragon-node@^2.0.1: version "2.1.1"
+ {STRING_SIGN_OUT_CONFIRMATION} +