mirror of
https://github.com/standardnotes/app.git
synced 2026-01-16 23:01:30 +00:00
Merge branch 'develop' into feature/multiple-selection
This commit is contained in:
commit
a1d56abd2e
24 changed files with 392 additions and 85 deletions
12
.github/codeql/codeql-config.yml
vendored
Normal file
12
.github/codeql/codeql-config.yml
vendored
Normal file
|
|
@ -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
|
||||
4
.github/codeql/custom-queries/javascript/qlpack.yml
vendored
Normal file
4
.github/codeql/custom-queries/javascript/qlpack.yml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
name: custom-javascript-queries
|
||||
version: 0.0.0
|
||||
libraryPathDependencies:
|
||||
- codeql-javascript
|
||||
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
|
@ -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
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -46,3 +46,5 @@ dump.rdb
|
|||
# Yarn
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
|
||||
codeqldb
|
||||
|
|
|
|||
|
|
@ -37,3 +37,5 @@ package-lock.json
|
|||
package.json
|
||||
Rakefile
|
||||
testing-server.js
|
||||
.github
|
||||
codeqldb
|
||||
|
|
|
|||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.github
|
||||
codeqldb
|
||||
|
|
@ -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]);
|
||||
|
|
|
|||
119
app/assets/javascripts/components/ConfirmSignoutModal.tsx
Normal file
119
app/assets/javascripts/components/ConfirmSignoutModal.tsx
Normal file
|
|
@ -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 <ConfirmSignoutModal {...props} />;
|
||||
});
|
||||
|
||||
const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
|
||||
const [deleteLocalBackups, setDeleteLocalBackups] = useState(
|
||||
application.hasAccount()
|
||||
);
|
||||
|
||||
const cancelRef = useRef<HTMLButtonElement>();
|
||||
function close() {
|
||||
appState.accountMenu.setSigningOut(false);
|
||||
}
|
||||
|
||||
const [localBackupsCount, setLocalBackupsCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
application.bridge.localBackupsCount().then(setLocalBackupsCount);
|
||||
}, [appState.accountMenu.signingOut, application.bridge]);
|
||||
|
||||
return (
|
||||
<AlertDialog onDismiss={close} leastDestructiveRef={cancelRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div className="sk-panel">
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title capitalize">
|
||||
End your session?
|
||||
</AlertDialogLabel>
|
||||
<AlertDialogDescription className="sk-panel-row">
|
||||
<p className="color-foreground">
|
||||
{STRING_SIGN_OUT_CONFIRMATION}
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
{localBackupsCount > 0 && (
|
||||
<div className="flex">
|
||||
<div className="sk-panel-row"></div>
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={deleteLocalBackups}
|
||||
onChange={(event) => {
|
||||
setDeleteLocalBackups(
|
||||
(event.target as HTMLInputElement).checked
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">
|
||||
Delete {localBackupsCount} local backup file
|
||||
{localBackupsCount > 1 ? 's' : ''}
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
className="capitalize sk-a ml-1.5 p-0 rounded cursor-pointer"
|
||||
onClick={() => {
|
||||
application.bridge.viewlocalBackups();
|
||||
}}
|
||||
>
|
||||
View backup files
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex my-1 mt-4">
|
||||
<button
|
||||
className="sn-button neutral"
|
||||
ref={cancelRef}
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="sn-button danger ml-2"
|
||||
onClick={() => {
|
||||
if (deleteLocalBackups) {
|
||||
application.signOutAndDeleteLocalBackups();
|
||||
} else {
|
||||
application.signOut();
|
||||
}
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{application.hasAccount()
|
||||
? 'Sign Out'
|
||||
: 'Clear Session Data'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialog>
|
||||
);
|
||||
});
|
||||
|
||||
export const ConfirmSignoutDirective = toDirective<Props>(
|
||||
ConfirmSignoutContainer
|
||||
);
|
||||
|
|
@ -84,6 +84,8 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
|
|||
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<unknown, AccountMenuState> {
|
|||
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<unknown, AccountMenuState> {
|
|||
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<unknown, AccountMenuState> {
|
|||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ export interface Bridge {
|
|||
environment: Environment;
|
||||
|
||||
getKeychainValue(): Promise<unknown>;
|
||||
setKeychainValue(value: any): Promise<void>;
|
||||
setKeychainValue(value: unknown): Promise<void>;
|
||||
clearKeychainValue(): Promise<void>;
|
||||
|
||||
localBackupsCount(): Promise<number>;
|
||||
viewlocalBackups(): void;
|
||||
deleteLocalBackups(): Promise<void>;
|
||||
|
||||
extensionsServerHost?: string;
|
||||
syncComponents(payloads: unknown[]): void;
|
||||
onMajorDataChange(): void;
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
async setKeychainValue(value: unknown): Promise<void> {
|
||||
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<number> {
|
||||
/** Browsers cannot save backups, only let you download one */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** No-ops */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
async deleteLocalBackups(): Promise<void> {}
|
||||
viewlocalBackups(): void {}
|
||||
syncComponents(): void {}
|
||||
onMajorDataChange(): void {}
|
||||
onInitialDataLoad(): void {}
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { WebApplication } from "../application";
|
|||
|
||||
export class SearchOptionsState {
|
||||
includeProtectedContents = false;
|
||||
includeArchived = false;
|
||||
includeArchived = true;
|
||||
includeTrashed = false;
|
||||
|
||||
constructor(
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
await this.bridge.deleteLocalBackups();
|
||||
return this.signOut();
|
||||
}
|
||||
|
||||
presentPasswordModal(callback: () => void) {
|
||||
const scope = this.scope!.$new(true) as InputModalScope;
|
||||
scope.type = "password";
|
||||
|
|
|
|||
|
|
@ -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<unknown, EditorState> {
|
|||
private saveTimeout?: ng.IPromise<void>;
|
||||
private statusTimeout?: ng.IPromise<void>;
|
||||
private lastEditorFocusEventSource?: EventSource;
|
||||
public editorValues: EditorValues = {};
|
||||
public editorValues: EditorValues = { title: '', text: '' };
|
||||
onEditorLoad?: () => void;
|
||||
|
||||
private tags: SNTag[] = [];
|
||||
|
|
@ -198,9 +203,12 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
* 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<unknown, EditorState> {
|
|||
});
|
||||
}
|
||||
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<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
}
|
||||
|
||||
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<unknown, EditorState> {
|
|||
}
|
||||
|
||||
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<unknown, EditorState> {
|
|||
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<unknown, EditorState> {
|
|||
}
|
||||
|
||||
restoreTrashedNote() {
|
||||
this.saveNote(
|
||||
this.save(
|
||||
this.note,
|
||||
copyEditorValues(this.editorValues),
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
|
|
@ -698,15 +733,31 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||
}
|
||||
|
||||
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<unknown, EditorState> {
|
|||
}
|
||||
|
||||
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<unknown, EditorState> {
|
|||
editor.selectionStart = editor.selectionEnd = start + 4;
|
||||
}
|
||||
this.editorValues.text = editor.value;
|
||||
this.saveNote(true);
|
||||
this.save(this.note, copyEditorValues(this.editorValues), true);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 32px;
|
||||
right: 36px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 0.15s linear;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
15
yarn.lock
15
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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue