Release/3.6.0 (#527)
* feat: (wip) authorize note access
* fix: remove multiEditorEnabled
* refactor: update SNJS + eslint
* refactor: remove privileges in favor of SNJS protections
* fix: do not close editor when editing an archived note
* chore: remove progress indicator for webpack dev server
* fix: add rel="noreferrer" to bugsnag links
* chore(deps): upgrade snjs
* chore(deps): upgrade snjs
* feat: batch manager protection + react challenge modal + eslint fix
* fix: lint errors
* fix: launch state error
* fix: challenge modal: cancel instead of dismiss when pressing escape
* feat: improve focus styles
* fix: cancel session revoking when pressing escape on confirm dialog
* fix: lint warning
* chore(deps): upgrade minor versions
* feat: make SNWebCrypto a constant
* feat: add random identifier to bugsnag reports
* fix: check onKeyUp instead of onKeyDown
* feat: implement SNJS backup file password retrieval
* chore(deps): upgrade snjs
* feat: display warning banner when using the app with no account
* fix: properly color svg button
* fix: wording
* fix: hide account warning after login + improve key storage wording
* chore(deps): upgrade stylekit
* feat: use stylekit fonts for the editor
* chore(deps): bump nokogiri from 1.10.8 to 1.11.1 (#511)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.8 to 1.11.1.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.8...v1.11.1)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com>
* chore(deps): bump ini from 1.3.5 to 1.3.8 (#504)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com>
* fix: rename master branch to main
* fix: add missing placeholders for submodules (#516)
Co-authored-by: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com>
* chore(deps): upgrade snjs, babel, typescript, reach, mobx, preact
* feat: clear protection session
* fix: use correct close icon size
* fix: hide protections paragraph when no account or passcode exist
* chore(deps): remove unused dependencies
* fix: button casing
* feat: implement SNApplication.hasProtectionSources
* chore(version): 3.6.0
* feat: enable sessions management for every build
* feat: make "Protected" flag more subtle
* fix: only match protected note title
* fix: remove inconsistencies between protected note label and date
* feat: show warning when protecting a note with no protection source
* feat: make unprotecting a note a protected action
* chore(deps): upgrade snjs
* chore(version): 3.6.0-beta01
* fix: run docker with root to fix crashing on Linux (undoes 62da387d3a) (#525)
* feat: make encrypted backups protected (#524)
Co-authored-by: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: proletarius101 <54175165+proletarius101@users.noreply.github.com>
Co-authored-by: Darius JJ Chuck <79410894+standarius@users.noreply.github.com>
Co-authored-by: Antonella Sgarlatta <antonella@standardnotes.org>
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"extends": ["eslint:recommended", "prettier"],
|
"root": true,
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
"plugin:react-hooks/recommended"
|
||||||
|
],
|
||||||
|
"plugins": ["@typescript-eslint", "react"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./app/assets/javascripts/tsconfig.json"
|
"project": "./app/assets/javascripts/tsconfig.json"
|
||||||
},
|
},
|
||||||
|
|||||||
5
.github/workflows/dev.yml
vendored
5
.github/workflows/dev.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
|
|
||||||
tsc:
|
tsc:
|
||||||
|
|
||||||
name: Check types
|
name: Check types & lint
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -22,6 +22,9 @@ jobs:
|
|||||||
- name: Typescript
|
- name: Typescript
|
||||||
run: yarn tsc
|
run: yarn tsc
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: yarn lint --quiet
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
9
.github/workflows/pr.yml
vendored
9
.github/workflows/pr.yml
vendored
@@ -7,12 +7,21 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
tsc:
|
tsc:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --pure-lockfile
|
run: yarn install --pure-lockfile
|
||||||
|
|
||||||
- name: Typescript
|
- name: Typescript
|
||||||
run: yarn tsc
|
run: yarn tsc
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: yarn lint --quiet
|
||||||
|
|||||||
5
.github/workflows/prod.yml
vendored
5
.github/workflows/prod.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
|
|
||||||
tsc:
|
tsc:
|
||||||
|
|
||||||
name: Check types
|
name: Check types & lint
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -23,6 +23,9 @@ jobs:
|
|||||||
- name: Typescript
|
- name: Typescript
|
||||||
run: yarn tsc
|
run: yarn tsc
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: yarn lint --quiet
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
15
Dockerfile
15
Dockerfile
@@ -1,10 +1,5 @@
|
|||||||
FROM ruby:2.7.1-alpine3.12
|
FROM ruby:2.7.1-alpine3.12
|
||||||
|
|
||||||
ARG UID=1000
|
|
||||||
ARG GID=1000
|
|
||||||
|
|
||||||
RUN addgroup -S webapp -g $GID && adduser -D -S webapp -G webapp -u $UID
|
|
||||||
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
alpine-sdk \
|
alpine-sdk \
|
||||||
nodejs-current \
|
nodejs-current \
|
||||||
@@ -16,19 +11,15 @@ RUN apk add --update --no-cache \
|
|||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
RUN chown -R $UID:$GID .
|
COPY package.json yarn.lock Gemfile Gemfile.lock /app/
|
||||||
|
|
||||||
USER webapp
|
COPY vendor /app/vendor
|
||||||
|
|
||||||
COPY --chown=$UID:$GID package.json yarn.lock Gemfile Gemfile.lock /app/
|
|
||||||
|
|
||||||
COPY --chown=$UID:$GID vendor /app/vendor
|
|
||||||
|
|
||||||
RUN yarn install --pure-lockfile
|
RUN yarn install --pure-lockfile
|
||||||
|
|
||||||
RUN gem install bundler && bundle install
|
RUN gem install bundler && bundle install
|
||||||
|
|
||||||
COPY --chown=$UID:$GID . /app/
|
COPY . /app/
|
||||||
|
|
||||||
RUN yarn bundle
|
RUN yarn bundle
|
||||||
|
|
||||||
|
|||||||
12
Gemfile.lock
12
Gemfile.lock
@@ -98,7 +98,7 @@ GEM
|
|||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
method_source (0.9.2)
|
method_source (0.9.2)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.5.0)
|
||||||
minitest (5.14.0)
|
minitest (5.14.0)
|
||||||
msgpack (1.3.3)
|
msgpack (1.3.3)
|
||||||
msgpack (1.3.3-x64-mingw32)
|
msgpack (1.3.3-x64-mingw32)
|
||||||
@@ -106,14 +106,16 @@ GEM
|
|||||||
net-ssh (>= 2.6.5, < 6.0.0)
|
net-ssh (>= 2.6.5, < 6.0.0)
|
||||||
net-ssh (5.2.0)
|
net-ssh (5.2.0)
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.8)
|
nokogiri (1.11.1)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.5.0)
|
||||||
nokogiri (1.10.8-x64-mingw32)
|
racc (~> 1.4)
|
||||||
mini_portile2 (~> 2.4.0)
|
nokogiri (1.11.1-x64-mingw32)
|
||||||
|
racc (~> 1.4)
|
||||||
non-stupid-digest-assets (1.0.9)
|
non-stupid-digest-assets (1.0.9)
|
||||||
sprockets (>= 2.0)
|
sprockets (>= 2.0)
|
||||||
puma (4.3.5)
|
puma (4.3.5)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
|
racc (1.5.2)
|
||||||
rack (2.2.3)
|
rack (2.2.3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
|
||||||
<!--
|
|
||||||
2019-4-11: Created with FontForge (http://fontforge.org)
|
|
||||||
-->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
|
||||||
<metadata>
|
|
||||||
Created by FontForge 20190318 at Thu Apr 11 10:47:26 2019
|
|
||||||
By Mo Bitar
|
|
||||||
Copyright (c) 2019, Mo Bitar
|
|
||||||
</metadata>
|
|
||||||
<defs>
|
|
||||||
<font id="Ionicons" horiz-adv-x="384" >
|
|
||||||
<font-face
|
|
||||||
font-family="Ionicons"
|
|
||||||
font-weight="400"
|
|
||||||
font-stretch="normal"
|
|
||||||
units-per-em="512"
|
|
||||||
panose-1="2 0 5 9 0 0 0 0 0 0"
|
|
||||||
ascent="448"
|
|
||||||
descent="-64"
|
|
||||||
bbox="0 -32 384 416"
|
|
||||||
underline-thickness="25.6"
|
|
||||||
underline-position="-51.2"
|
|
||||||
unicode-range="U+F200-F266"
|
|
||||||
/>
|
|
||||||
<missing-glyph />
|
|
||||||
<glyph glyph-name="ion-arrow-return-right" unicode=""
|
|
||||||
d="M384 256l-128 -96v64h-192v-128h248c4 0 8 -4 8 -8v-48c0 -4 -4 -8 -8 -8h-304c-4 0 -8 4 -8 8v240c0 4 4 8 8 8h248v64z" />
|
|
||||||
<glyph glyph-name="ion-arrow-return-left" unicode=""
|
|
||||||
d="M128 352v-64h248c4 0 8 -4 8 -8v-240c0 -4 -4 -8 -8 -8h-304c-4 0 -8 4 -8 8v48c0 4 4 8 8 8h248v128h-192v-64l-128 96z" />
|
|
||||||
<glyph glyph-name="ion-plus" unicode=""
|
|
||||||
d="M384 224v-64h-160v-160h-64v160h-160v64h160v160h64v-160h160z" />
|
|
||||||
<glyph glyph-name="ion-locked" unicode=""
|
|
||||||
d="M22 -32c-12 0 -22 10 -22 22v212c0 12 10 22 22 22h3h19v31c0 42 17 87 43 115s64 46 105 46v0v0c41 0 79 -18 105 -46s43 -73 43 -115v-31h22c12 0 22 -10 22 -22v-212c0 -12 -10 -22 -22 -22h-340zM97 255v-31h17h155h18v31c0 27 -10 61 -28 80v0v1
|
|
||||||
c-18 19 -42 29 -67 29v0v0c-25 0 -49 -10 -67 -29v-1v0c-18 -19 -28 -53 -28 -80z" />
|
|
||||||
</font>
|
|
||||||
</defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
3
app/assets/icons/ic_close.svg
Normal file
3
app/assets/icons/ic_close.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.2459 5.92925C15.5704 5.60478 15.5704 5.07872 15.2459 4.75425C14.9214 4.42978 14.3954 4.42978 14.0709 4.75425L10.0001 8.82508L5.92925 4.75425C5.60478 4.42978 5.07872 4.42978 4.75425 4.75425C4.42978 5.07872 4.42978 5.60478 4.75425 5.92925L8.82508 10.0001L4.75425 14.0709C4.42978 14.3954 4.42978 14.9214 4.75425 15.2459C5.07872 15.5704 5.60478 15.5704 5.92925 15.2459L10.0001 11.1751L14.0709 15.2459C14.3954 15.5704 14.9214 15.5704 15.2459 15.2459C15.5704 14.9214 15.5704 14.3954 15.2459 14.0709L11.1751 10.0001L15.2459 5.92925Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 646 B |
3
app/assets/javascripts/@types/modules.ts
Normal file
3
app/assets/javascripts/@types/modules.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare module '*.svg' {
|
||||||
|
export default function SvgComponent(props: React.SVGProps<SVGSVGElement>): JSX.Element;
|
||||||
|
}
|
||||||
@@ -44,8 +44,6 @@ import {
|
|||||||
PanelResizer,
|
PanelResizer,
|
||||||
PasswordWizard,
|
PasswordWizard,
|
||||||
PermissionsModal,
|
PermissionsModal,
|
||||||
PrivilegesAuthModal,
|
|
||||||
PrivilegesManagementModal,
|
|
||||||
RevisionPreviewModal,
|
RevisionPreviewModal,
|
||||||
HistoryMenu,
|
HistoryMenu,
|
||||||
SyncResolutionMenu,
|
SyncResolutionMenu,
|
||||||
@@ -57,7 +55,8 @@ import { BrowserBridge } from './services/browserBridge';
|
|||||||
import { startErrorReporting } from './services/errorReporting';
|
import { startErrorReporting } from './services/errorReporting';
|
||||||
import { StartApplication } from './startApplication';
|
import { StartApplication } from './startApplication';
|
||||||
import { Bridge } from './services/bridge';
|
import { Bridge } from './services/bridge';
|
||||||
import { SessionsModalDirective } from './directives/views/sessionsModal';
|
import { SessionsModalDirective } from './components/SessionsModal';
|
||||||
|
import { NoAccountWarningDirective } from './components/NoAccountWarning';
|
||||||
|
|
||||||
|
|
||||||
function reloadHiddenFirefoxTab(): boolean {
|
function reloadHiddenFirefoxTab(): boolean {
|
||||||
@@ -140,15 +139,11 @@ const startApplication: StartApplication = async function startApplication(
|
|||||||
.directive('panelResizer', () => new PanelResizer())
|
.directive('panelResizer', () => new PanelResizer())
|
||||||
.directive('passwordWizard', () => new PasswordWizard())
|
.directive('passwordWizard', () => new PasswordWizard())
|
||||||
.directive('permissionsModal', () => new PermissionsModal())
|
.directive('permissionsModal', () => new PermissionsModal())
|
||||||
.directive('privilegesAuthModal', () => new PrivilegesAuthModal())
|
|
||||||
.directive(
|
|
||||||
'privilegesManagementModal',
|
|
||||||
() => new PrivilegesManagementModal()
|
|
||||||
)
|
|
||||||
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
|
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
|
||||||
.directive('historyMenu', () => new HistoryMenu())
|
.directive('historyMenu', () => new HistoryMenu())
|
||||||
.directive('syncResolutionMenu', () => new SyncResolutionMenu())
|
.directive('syncResolutionMenu', () => new SyncResolutionMenu())
|
||||||
.directive('sessionsModal', SessionsModalDirective);
|
.directive('sessionsModal', SessionsModalDirective)
|
||||||
|
.directive('noAccountWarning', NoAccountWarningDirective);
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
angular.module('app').filter('trusted', ['$sce', trusted]);
|
angular.module('app').filter('trusted', ['$sce', trusted]);
|
||||||
|
|||||||
39
app/assets/javascripts/components/NoAccountWarning.tsx
Normal file
39
app/assets/javascripts/components/NoAccountWarning.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { toDirective, useAutorunValue } from './utils';
|
||||||
|
import Close from '../../icons/ic_close.svg';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
|
||||||
|
function NoAccountWarning({ appState }: { appState: AppState }) {
|
||||||
|
const canShow = useAutorunValue(() => appState.noAccountWarning.show);
|
||||||
|
if (!canShow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mt-5 p-5 rounded-md shadow-sm grid grid-template-cols-1fr">
|
||||||
|
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
|
||||||
|
<p className="m-0 mt-1 col-start-1 col-end-3">
|
||||||
|
Sign in or register to back up your notes.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="sn-btn mt-3 col-start-1 col-end-3 justify-self-start"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
appState.accountMenu.setShow(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Account menu
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
appState.noAccountWarning.hide();
|
||||||
|
}}
|
||||||
|
title="Ignore"
|
||||||
|
label="Ignore"
|
||||||
|
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
|
||||||
|
>
|
||||||
|
<Close className="fill-current" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoAccountWarningDirective = toDirective(NoAccountWarning);
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { PureViewCtrl } from '@/views';
|
|
||||||
import {
|
import {
|
||||||
SNApplication,
|
SNApplication,
|
||||||
RemoteSession,
|
|
||||||
SessionStrings,
|
SessionStrings,
|
||||||
UuidString,
|
UuidString,
|
||||||
|
isNullOrUndefined,
|
||||||
|
RemoteSession,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import { autorun, IAutorunOptions, IReactionPublic } from 'mobx';
|
import { FunctionComponent } from 'preact';
|
||||||
import { render, FunctionComponent } from 'preact';
|
import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
|
||||||
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
||||||
import { Dialog } from '@reach/dialog';
|
import { Dialog } from '@reach/dialog';
|
||||||
import { Alert } from '@reach/alert';
|
import { Alert } from '@reach/alert';
|
||||||
import {
|
import {
|
||||||
@@ -16,10 +15,8 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogLabel,
|
AlertDialogLabel,
|
||||||
} from '@reach/alert-dialog';
|
} from '@reach/alert-dialog';
|
||||||
|
import { toDirective, useAutorun } from './utils';
|
||||||
function useAutorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions) {
|
import { WebApplication } from '@/ui_models/application';
|
||||||
useEffect(() => autorun(view, opts), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Session = RemoteSession & {
|
type Session = RemoteSession & {
|
||||||
revoking?: true;
|
revoking?: true;
|
||||||
@@ -56,16 +53,16 @@ function useSessions(
|
|||||||
}
|
}
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
})();
|
})();
|
||||||
}, [lastRefreshDate]);
|
}, [application, lastRefreshDate]);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
setLastRefreshDate(Date.now());
|
setLastRefreshDate(Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function revokeSession(uuid: UuidString) {
|
async function revokeSession(uuid: UuidString) {
|
||||||
const responsePromise = application.revokeSession(uuid);
|
const sessionsBeforeRevoke = sessions;
|
||||||
|
|
||||||
let sessionsBeforeRevoke = sessions;
|
const responsePromise = application.revokeSession(uuid);
|
||||||
|
|
||||||
const sessionsDuringRevoke = sessions.slice();
|
const sessionsDuringRevoke = sessions.slice();
|
||||||
const toRemoveIndex = sessions.findIndex(
|
const toRemoveIndex = sessions.findIndex(
|
||||||
@@ -78,7 +75,9 @@ function useSessions(
|
|||||||
setSessions(sessionsDuringRevoke);
|
setSessions(sessionsDuringRevoke);
|
||||||
|
|
||||||
const response = await responsePromise;
|
const response = await responsePromise;
|
||||||
if ('error' in response) {
|
if (isNullOrUndefined(response)) {
|
||||||
|
setSessions(sessionsBeforeRevoke);
|
||||||
|
} else if ('error' in response) {
|
||||||
if (response.error?.message) {
|
if (response.error?.message) {
|
||||||
setErrorMessage(response.error?.message);
|
setErrorMessage(response.error?.message);
|
||||||
} else {
|
} else {
|
||||||
@@ -111,19 +110,23 @@ const SessionsModal: FunctionComponent<{
|
|||||||
const closeRevokeSessionAlert = () => setRevokingSessionUuid('');
|
const closeRevokeSessionAlert = () => setRevokingSessionUuid('');
|
||||||
const cancelRevokeRef = useRef<HTMLButtonElement>();
|
const cancelRevokeRef = useRef<HTMLButtonElement>();
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
const formatter = useMemo(
|
||||||
|
() =>
|
||||||
|
new Intl.DateTimeFormat(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
});
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog onDismiss={close}>
|
<Dialog onDismiss={close} className="sessions-modal">
|
||||||
<div className="sk-modal-content sessions-modal">
|
<div className="sk-modal-content">
|
||||||
<div class="sn-component">
|
<div class="sn-component">
|
||||||
<div class="sk-panel">
|
<div class="sk-panel">
|
||||||
<div class="sk-panel-header">
|
<div class="sk-panel-header">
|
||||||
@@ -190,7 +193,12 @@ const SessionsModal: FunctionComponent<{
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{confirmRevokingSessionUuid && (
|
{confirmRevokingSessionUuid && (
|
||||||
<AlertDialog leastDestructiveRef={cancelRevokeRef}>
|
<AlertDialog
|
||||||
|
onDismiss={() => {
|
||||||
|
setRevokingSessionUuid('');
|
||||||
|
}}
|
||||||
|
leastDestructiveRef={cancelRevokeRef}
|
||||||
|
>
|
||||||
<div className="sk-modal-content">
|
<div className="sk-modal-content">
|
||||||
<div className="sn-component">
|
<div className="sn-component">
|
||||||
<div className="sk-panel">
|
<div className="sk-panel">
|
||||||
@@ -235,7 +243,7 @@ const SessionsModal: FunctionComponent<{
|
|||||||
|
|
||||||
const Sessions: FunctionComponent<{
|
const Sessions: FunctionComponent<{
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
application: SNApplication;
|
application: WebApplication;
|
||||||
}> = ({ appState, application }) => {
|
}> = ({ appState, application }) => {
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
useAutorun(() => setShowModal(appState.isSessionsModalVisible));
|
useAutorun(() => setShowModal(appState.isSessionsModalVisible));
|
||||||
@@ -247,26 +255,4 @@ const Sessions: FunctionComponent<{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class SessionsModalCtrl extends PureViewCtrl<{}, {}> {
|
export const SessionsModalDirective = toDirective(Sessions);
|
||||||
/* @ngInject */
|
|
||||||
constructor(private $element: JQLite, $timeout: ng.ITimeoutService) {
|
|
||||||
super($timeout);
|
|
||||||
this.$element = $element;
|
|
||||||
}
|
|
||||||
$onChanges() {
|
|
||||||
render(
|
|
||||||
<Sessions appState={this.appState} application={this.application} />,
|
|
||||||
this.$element[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SessionsModalDirective() {
|
|
||||||
return {
|
|
||||||
controller: SessionsModalCtrl,
|
|
||||||
bindToController: true,
|
|
||||||
scope: {
|
|
||||||
application: '=',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
56
app/assets/javascripts/components/utils.ts
Normal file
56
app/assets/javascripts/components/utils.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
import { autorun, IAutorunOptions, IReactionPublic } from 'mobx';
|
||||||
|
import { FunctionComponent, h, render } from 'preact';
|
||||||
|
import { useEffect } from 'preact/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export function useAutorunValue<T>(query: () => T): T {
|
||||||
|
const [value, setValue] = useState(query);
|
||||||
|
useAutorun(() => {
|
||||||
|
setValue(query());
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAutorun(
|
||||||
|
view: (r: IReactionPublic) => unknown,
|
||||||
|
opts?: IAutorunOptions
|
||||||
|
): void {
|
||||||
|
useEffect(() => autorun(view, opts), [view, opts]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDirective(
|
||||||
|
component: FunctionComponent<{
|
||||||
|
application: WebApplication;
|
||||||
|
appState: AppState;
|
||||||
|
}>
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
return function () {
|
||||||
|
return {
|
||||||
|
controller: [
|
||||||
|
'$element',
|
||||||
|
'$scope',
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
($element: JQLite, $scope: any) => {
|
||||||
|
return {
|
||||||
|
$onChanges() {
|
||||||
|
render(
|
||||||
|
h(component, {
|
||||||
|
application: $scope.application,
|
||||||
|
appState: $scope.appState,
|
||||||
|
}),
|
||||||
|
$element[0]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scope: {
|
||||||
|
application: '=',
|
||||||
|
appState: '=',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
3
app/assets/javascripts/crypto.ts
Normal file
3
app/assets/javascripts/crypto.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { SNWebCrypto } from "@standardnotes/sncrypto-web";
|
||||||
|
|
||||||
|
export const WebCrypto = new SNWebCrypto();
|
||||||
@@ -135,6 +135,7 @@ export class Database {
|
|||||||
const db = (await this.openDatabase())!;
|
const db = (await this.openDatabase())!;
|
||||||
const transaction = db.transaction(STORE_NAME, READ_WRITE);
|
const transaction = db.transaction(STORE_NAME, READ_WRITE);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
transaction.oncomplete = () => { };
|
transaction.oncomplete = () => { };
|
||||||
transaction.onerror = (event) => {
|
transaction.onerror = (event) => {
|
||||||
const target = event!.target! as any;
|
const target = event!.target! as any;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function clickOutside($document: ng.IDocumentService) {
|
|||||||
$scope.$apply(attrs.clickOutside);
|
$scope.$apply(attrs.clickOutside);
|
||||||
didApplyClickOutside = true;
|
didApplyClickOutside = true;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$scope.$on('$destroy', () => {
|
$scope.$on('$destroy', () => {
|
||||||
attrs.clickOutside = undefined;
|
attrs.clickOutside = undefined;
|
||||||
|
|||||||
@@ -16,23 +16,23 @@ export function delayHide($timeout: ng.ITimeoutService) {
|
|||||||
scopeAny.hidePromise = null;
|
scopeAny.hidePromise = null;
|
||||||
}
|
}
|
||||||
showElement(true);
|
showElement(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
const hideSpinner = () => {
|
const hideSpinner = () => {
|
||||||
scopeAny.hidePromise = $timeout(
|
scopeAny.hidePromise = $timeout(
|
||||||
showElement.bind(this as any, false),
|
showElement.bind(this as any, false),
|
||||||
getDelay()
|
getDelay()
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const showElement = (show: boolean) => {
|
const showElement = (show: boolean) => {
|
||||||
show ? elem.css({ display: '' }) : elem.css({ display: 'none' });
|
show ? elem.css({ display: '' }) : elem.css({ display: 'none' });
|
||||||
}
|
};
|
||||||
|
|
||||||
const getDelay = () => {
|
const getDelay = () => {
|
||||||
const delay = parseInt(scopeAny.delay);
|
const delay = parseInt(scopeAny.delay);
|
||||||
return angular.isNumber(delay) ? delay : 200;
|
return angular.isNumber(delay) ? delay : 200;
|
||||||
}
|
};
|
||||||
|
|
||||||
showElement(false);
|
showElement(false);
|
||||||
// Whenever the scope variable updates we simply
|
// Whenever the scope variable updates we simply
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export function elemReady($parse: ng.IParseService) {
|
|||||||
link: function($scope: ng.IScope, elem: JQLite, attrs: any) {
|
link: function($scope: ng.IScope, elem: JQLite, attrs: any) {
|
||||||
elem.ready(function() {
|
elem.ready(function() {
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
var func = $parse(attrs.elemReady);
|
const func = $parse(attrs.elemReady);
|
||||||
func($scope);
|
func($scope);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function infiniteScroll() {
|
|||||||
};
|
};
|
||||||
elem.on('scroll', scopeAny.onScroll);
|
elem.on('scroll', scopeAny.onScroll);
|
||||||
scope.$on('$destroy', () => {
|
scope.$on('$destroy', () => {
|
||||||
elem.off('scroll', scopeAny.onScroll);;
|
elem.off('scroll', scopeAny.onScroll);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { WebDirective } from './../../types';
|
import { WebDirective } from './../../types';
|
||||||
import { isDesktopApplication, preventRefreshing } from '@/utils';
|
import { isDesktopApplication, isSameDay, preventRefreshing } from '@/utils';
|
||||||
import template from '%/directives/account-menu.pug';
|
import template from '%/directives/account-menu.pug';
|
||||||
import { ProtectedAction, ContentType } from '@standardnotes/snjs';
|
|
||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||||
import {
|
import {
|
||||||
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
||||||
@@ -10,8 +9,6 @@ import {
|
|||||||
STRING_LOCAL_ENC_ENABLED,
|
STRING_LOCAL_ENC_ENABLED,
|
||||||
STRING_ENC_NOT_ENABLED,
|
STRING_ENC_NOT_ENABLED,
|
||||||
STRING_IMPORT_SUCCESS,
|
STRING_IMPORT_SUCCESS,
|
||||||
STRING_REMOVE_PASSCODE_CONFIRMATION,
|
|
||||||
STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM,
|
|
||||||
STRING_NON_MATCHING_PASSCODES,
|
STRING_NON_MATCHING_PASSCODES,
|
||||||
STRING_NON_MATCHING_PASSWORDS,
|
STRING_NON_MATCHING_PASSWORDS,
|
||||||
STRING_INVALID_IMPORT_FILE,
|
STRING_INVALID_IMPORT_FILE,
|
||||||
@@ -20,38 +17,47 @@ import {
|
|||||||
StringImportError,
|
StringImportError,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
||||||
STRING_UNSUPPORTED_BACKUP_FILE_VERSION
|
STRING_UNSUPPORTED_BACKUP_FILE_VERSION,
|
||||||
|
Strings,
|
||||||
} from '@/strings';
|
} from '@/strings';
|
||||||
import { PasswordWizardType } from '@/types';
|
import { PasswordWizardType } from '@/types';
|
||||||
import { BackupFile } from '@standardnotes/snjs';
|
import {
|
||||||
|
ApplicationEvent,
|
||||||
|
BackupFile,
|
||||||
|
ContentType,
|
||||||
|
Platform,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
import { confirmDialog, alertDialog } from '@/services/alertService';
|
import { confirmDialog, alertDialog } from '@/services/alertService';
|
||||||
import { autorun, IReactionDisposer } from 'mobx';
|
import { autorun, IReactionDisposer } from 'mobx';
|
||||||
import { storage, StorageKey } from '@/services/localStorage';
|
import { storage, StorageKey } from '@/services/localStorage';
|
||||||
|
import {
|
||||||
const ELEMENT_ID_IMPORT_PASSWORD_INPUT = 'import-password-request';
|
disableErrorReporting,
|
||||||
|
enableErrorReporting,
|
||||||
|
errorReportingId,
|
||||||
|
} from '@/services/errorReporting';
|
||||||
|
|
||||||
const ELEMENT_NAME_AUTH_EMAIL = 'email';
|
const ELEMENT_NAME_AUTH_EMAIL = 'email';
|
||||||
const ELEMENT_NAME_AUTH_PASSWORD = 'password';
|
const ELEMENT_NAME_AUTH_PASSWORD = 'password';
|
||||||
const ELEMENT_NAME_AUTH_PASSWORD_CONF = 'password_conf';
|
const ELEMENT_NAME_AUTH_PASSWORD_CONF = 'password_conf';
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
email: string
|
email: string;
|
||||||
user_password: string
|
user_password: string;
|
||||||
password_conf: string
|
password_conf: string;
|
||||||
confirmPassword: boolean
|
confirmPassword: boolean;
|
||||||
showLogin: boolean
|
showLogin: boolean;
|
||||||
showRegister: boolean
|
showRegister: boolean;
|
||||||
showPasscodeForm: boolean
|
showPasscodeForm: boolean;
|
||||||
strictSignin?: boolean
|
strictSignin?: boolean;
|
||||||
ephemeral: boolean
|
ephemeral: boolean;
|
||||||
mergeLocal?: boolean
|
mergeLocal?: boolean;
|
||||||
url: string
|
url: string;
|
||||||
authenticating: boolean
|
authenticating: boolean;
|
||||||
status: string
|
status: string;
|
||||||
passcode: string
|
passcode: string;
|
||||||
confirmPasscode: string
|
confirmPasscode: string;
|
||||||
changingPasscode: boolean
|
changingPasscode: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
type AccountMenuState = {
|
type AccountMenuState = {
|
||||||
formData: Partial<FormData>;
|
formData: Partial<FormData>;
|
||||||
@@ -60,30 +66,30 @@ type AccountMenuState = {
|
|||||||
user: any;
|
user: any;
|
||||||
mutable: any;
|
mutable: any;
|
||||||
importData: any;
|
importData: any;
|
||||||
encryptionStatusString: string;
|
encryptionStatusString?: string;
|
||||||
server: string;
|
server?: string;
|
||||||
encryptionEnabled: boolean;
|
encryptionEnabled?: boolean;
|
||||||
selectedAutoLockInterval: any;
|
selectedAutoLockInterval?: unknown;
|
||||||
showBetaWarning: boolean;
|
showBetaWarning: boolean;
|
||||||
errorReportingEnabled: boolean;
|
errorReportingEnabled: boolean;
|
||||||
syncInProgress: boolean;
|
syncInProgress: boolean;
|
||||||
syncError: string;
|
syncError?: string;
|
||||||
showSessions: boolean;
|
showSessions: boolean;
|
||||||
}
|
errorReportingId: string | null;
|
||||||
|
keyStorageInfo: string | null;
|
||||||
|
protectionsDisabledUntil: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
|
||||||
|
public appVersion: string;
|
||||||
public appVersion: string
|
|
||||||
/** @template */
|
/** @template */
|
||||||
private closeFunction?: () => void
|
private closeFunction?: () => void;
|
||||||
private removeBetaWarningListener?: IReactionDisposer
|
private removeBetaWarningListener?: IReactionDisposer;
|
||||||
private removeSyncObserver?: IReactionDisposer
|
private removeSyncObserver?: IReactionDisposer;
|
||||||
|
private removeProtectionLengthObserver?: () => void;
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor($timeout: ng.ITimeoutService, appVersion: string) {
|
||||||
$timeout: ng.ITimeoutService,
|
|
||||||
appVersion: string,
|
|
||||||
) {
|
|
||||||
super($timeout);
|
super($timeout);
|
||||||
this.appVersion = appVersion;
|
this.appVersion = appVersion;
|
||||||
}
|
}
|
||||||
@@ -92,17 +98,25 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
appVersion: 'v' + ((window as any).electronAppVersion || this.appVersion),
|
appVersion: 'v' + ((window as any).electronAppVersion || this.appVersion),
|
||||||
passcodeAutoLockOptions: this.application!.getAutolockService().getAutoLockIntervalOptions(),
|
passcodeAutoLockOptions: this.application
|
||||||
user: this.application!.getUser(),
|
.getAutolockService()
|
||||||
|
.getAutoLockIntervalOptions(),
|
||||||
|
user: this.application.getUser(),
|
||||||
formData: {
|
formData: {
|
||||||
mergeLocal: true,
|
mergeLocal: true,
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
},
|
},
|
||||||
mutable: {},
|
mutable: {},
|
||||||
showBetaWarning: false,
|
showBetaWarning: false,
|
||||||
errorReportingEnabled: storage.get(StorageKey.DisableErrorReporting) === false,
|
errorReportingEnabled:
|
||||||
|
storage.get(StorageKey.DisableErrorReporting) === false,
|
||||||
showSessions: false,
|
showSessions: false,
|
||||||
} as AccountMenuState;
|
errorReportingId: errorReportingId(),
|
||||||
|
keyStorageInfo: Strings.keyStorageInfo(this.application),
|
||||||
|
importData: null,
|
||||||
|
syncInProgress: false,
|
||||||
|
protectionsDisabledUntil: this.getProtectionsDisabledUntil(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getState() {
|
getState() {
|
||||||
@@ -124,17 +138,17 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
|
|
||||||
refreshedCredentialState() {
|
refreshedCredentialState() {
|
||||||
return {
|
return {
|
||||||
user: this.application!.getUser(),
|
user: this.application.getUser(),
|
||||||
canAddPasscode: !this.application!.isEphemeralSession(),
|
canAddPasscode: !this.application.isEphemeralSession(),
|
||||||
hasPasscode: this.application!.hasPasscode(),
|
hasPasscode: this.application.hasPasscode(),
|
||||||
showPasscodeForm: false
|
showPasscodeForm: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async $onInit() {
|
async $onInit() {
|
||||||
super.$onInit();
|
super.$onInit();
|
||||||
this.setState({
|
this.setState({
|
||||||
showSessions: this.appState.enableUnfinishedFeatures && await this.application.userCanManageSessions()
|
showSessions: await this.application.userCanManageSessions()
|
||||||
});
|
});
|
||||||
|
|
||||||
const sync = this.appState.sync;
|
const sync = this.appState.sync;
|
||||||
@@ -143,17 +157,27 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
syncInProgress: sync.inProgress,
|
syncInProgress: sync.inProgress,
|
||||||
syncError: sync.errorMessage,
|
syncError: sync.errorMessage,
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
this.removeBetaWarningListener = autorun(() => {
|
this.removeBetaWarningListener = autorun(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showBetaWarning: this.appState.showBetaWarning
|
showBetaWarning: this.appState.showBetaWarning,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.removeProtectionLengthObserver = this.application.addEventObserver(
|
||||||
|
async () => {
|
||||||
|
this.setState({
|
||||||
|
protectionsDisabledUntil: this.getProtectionsDisabledUntil(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ApplicationEvent.ProtectionSessionExpiryDateChanged
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.removeSyncObserver?.();
|
this.removeSyncObserver?.();
|
||||||
this.removeBetaWarningListener?.();
|
this.removeBetaWarningListener?.();
|
||||||
|
this.removeProtectionLengthObserver?.();
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,17 +187,50 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasProtections() {
|
||||||
|
return this.application.hasProtectionSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProtectionsDisabledUntil(): string | null {
|
||||||
|
const protectionExpiry = this.application.getProtectionSessionExpiryDate();
|
||||||
|
const now = new Date();
|
||||||
|
if (protectionExpiry > now) {
|
||||||
|
let f: Intl.DateTimeFormat;
|
||||||
|
if (isSameDay(protectionExpiry, now)) {
|
||||||
|
f = new Intl.DateTimeFormat(undefined, {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
f = new Intl.DateTimeFormat(undefined, {
|
||||||
|
weekday: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.format(protectionExpiry);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async loadHost() {
|
async loadHost() {
|
||||||
const host = await this.application!.getHost();
|
const host = await this.application.getHost();
|
||||||
this.setState({
|
this.setState({
|
||||||
server: host,
|
server: host,
|
||||||
formData: {
|
formData: {
|
||||||
...this.getState().formData,
|
...this.getState().formData,
|
||||||
url: host
|
url: host,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableProtections() {
|
||||||
|
this.application.clearProtectionSession();
|
||||||
|
}
|
||||||
|
|
||||||
onHostInputChange() {
|
onHostInputChange() {
|
||||||
const url = this.getState().formData.url!;
|
const url = this.getState().formData.url!;
|
||||||
this.application!.setHost(url);
|
this.application!.setHost(url);
|
||||||
@@ -193,8 +250,8 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
encryptionEnabled,
|
encryptionEnabled,
|
||||||
mutable: {
|
mutable: {
|
||||||
...this.getState().mutable,
|
...this.getState().mutable,
|
||||||
backupEncrypted: encryptionEnabled
|
backupEncrypted: encryptionEnabled,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +263,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
const names = [
|
const names = [
|
||||||
ELEMENT_NAME_AUTH_EMAIL,
|
ELEMENT_NAME_AUTH_EMAIL,
|
||||||
ELEMENT_NAME_AUTH_PASSWORD,
|
ELEMENT_NAME_AUTH_PASSWORD,
|
||||||
ELEMENT_NAME_AUTH_PASSWORD_CONF
|
ELEMENT_NAME_AUTH_PASSWORD_CONF,
|
||||||
];
|
];
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
const element = document.getElementsByName(name)[0];
|
const element = document.getElementsByName(name)[0];
|
||||||
@@ -217,7 +274,10 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitAuthForm() {
|
submitAuthForm() {
|
||||||
if (!this.getState().formData.email || !this.getState().formData.user_password) {
|
if (
|
||||||
|
!this.getState().formData.email ||
|
||||||
|
!this.getState().formData.user_password
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.blurAuthFields();
|
this.blurAuthFields();
|
||||||
@@ -232,15 +292,15 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
return this.setState({
|
return this.setState({
|
||||||
formData: {
|
formData: {
|
||||||
...this.getState().formData,
|
...this.getState().formData,
|
||||||
...formData
|
...formData,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async login() {
|
async login() {
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
status: STRING_GENERATING_LOGIN_KEYS,
|
status: STRING_GENERATING_LOGIN_KEYS,
|
||||||
authenticating: true
|
authenticating: true,
|
||||||
});
|
});
|
||||||
const formData = this.getState().formData;
|
const formData = this.getState().formData;
|
||||||
const response = await this.application!.signIn(
|
const response = await this.application!.signIn(
|
||||||
@@ -254,7 +314,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
if (!error) {
|
if (!error) {
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
authenticating: false,
|
authenticating: false,
|
||||||
user_password: undefined
|
user_password: undefined,
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
@@ -262,28 +322,26 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
showLogin: true,
|
showLogin: true,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
user_password: undefined
|
user_password: undefined,
|
||||||
});
|
});
|
||||||
if (error.message) {
|
if (error.message) {
|
||||||
this.application!.alertService!.alert(error.message);
|
this.application!.alertService!.alert(error.message);
|
||||||
}
|
}
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
authenticating: false
|
authenticating: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async register() {
|
async register() {
|
||||||
const confirmation = this.getState().formData.password_conf;
|
const confirmation = this.getState().formData.password_conf;
|
||||||
if (confirmation !== this.getState().formData.user_password) {
|
if (confirmation !== this.getState().formData.user_password) {
|
||||||
this.application!.alertService!.alert(
|
this.application!.alertService!.alert(STRING_NON_MATCHING_PASSWORDS);
|
||||||
STRING_NON_MATCHING_PASSWORDS
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
confirmPassword: false,
|
confirmPassword: false,
|
||||||
status: STRING_GENERATING_REGISTER_KEYS,
|
status: STRING_GENERATING_REGISTER_KEYS,
|
||||||
authenticating: true
|
authenticating: true,
|
||||||
});
|
});
|
||||||
const response = await this.application!.register(
|
const response = await this.application!.register(
|
||||||
this.getState().formData.email!,
|
this.getState().formData.email!,
|
||||||
@@ -294,14 +352,12 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
const error = response.error;
|
const error = response.error;
|
||||||
if (error) {
|
if (error) {
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
status: undefined
|
status: undefined,
|
||||||
});
|
});
|
||||||
await this.setFormDataState({
|
await this.setFormDataState({
|
||||||
authenticating: false
|
authenticating: false,
|
||||||
});
|
});
|
||||||
this.application!.alertService!.alert(
|
this.application!.alertService!.alert(error.message);
|
||||||
error.message
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await this.setFormDataState({ authenticating: false });
|
await this.setFormDataState({ authenticating: false });
|
||||||
this.close();
|
this.close();
|
||||||
@@ -313,8 +369,8 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
mergeLocal: !(await confirmDialog({
|
mergeLocal: !(await confirmDialog({
|
||||||
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
||||||
confirmButtonStyle: 'danger'
|
confirmButtonStyle: 'danger',
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,45 +385,20 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
this.appState.openSessionsModal();
|
this.appState.openSessionsModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
async openPrivilegesModal() {
|
|
||||||
const run = () => {
|
|
||||||
this.application!.presentPrivilegesManagementModal();
|
|
||||||
this.close();
|
|
||||||
};
|
|
||||||
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManagePrivileges
|
|
||||||
);
|
|
||||||
if (needsPrivilege) {
|
|
||||||
this.application!.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManagePrivileges,
|
|
||||||
() => {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroyLocalData() {
|
async destroyLocalData() {
|
||||||
if (await confirmDialog({
|
if (
|
||||||
|
await confirmDialog({
|
||||||
text: STRING_SIGN_OUT_CONFIRMATION,
|
text: STRING_SIGN_OUT_CONFIRMATION,
|
||||||
confirmButtonStyle: "danger"
|
confirmButtonStyle: 'danger',
|
||||||
})) {
|
})
|
||||||
|
) {
|
||||||
this.application.signOut();
|
this.application.signOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitImportPassword() {
|
|
||||||
await this.performImport(
|
|
||||||
this.getState().importData.data,
|
|
||||||
this.getState().importData.password
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showRegister() {
|
showRegister() {
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
showRegister: true
|
showRegister: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,9 +410,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
const data = JSON.parse(e.target!.result as string);
|
const data = JSON.parse(e.target!.result as string);
|
||||||
resolve(data);
|
resolve(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.application!.alertService!.alert(
|
this.application!.alertService!.alert(STRING_INVALID_IMPORT_FILE);
|
||||||
STRING_INVALID_IMPORT_FILE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
@@ -392,128 +421,84 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
* @template
|
* @template
|
||||||
*/
|
*/
|
||||||
async importFileSelected(files: File[]) {
|
async importFileSelected(files: File[]) {
|
||||||
const run = async () => {
|
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
const data = await this.readFile(file);
|
const data = await this.readFile(file);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.version || data.auth_params || data.keyParams) {
|
if (data.version || data.auth_params || data.keyParams) {
|
||||||
const version = data.version || data.keyParams?.version || data.auth_params?.version;
|
const version =
|
||||||
|
data.version || data.keyParams?.version || data.auth_params?.version;
|
||||||
if (
|
if (
|
||||||
!this.application!.protocolService!.supportedVersions().includes(version)
|
this.application.protocolService.supportedVersions().includes(version)
|
||||||
) {
|
) {
|
||||||
|
await this.performImport(data);
|
||||||
|
} else {
|
||||||
await this.setState({ importData: null });
|
await this.setState({ importData: null });
|
||||||
alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION });
|
void alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION });
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.keyParams || data.auth_params) {
|
|
||||||
await this.setState({
|
|
||||||
importData: {
|
|
||||||
...this.getState().importData,
|
|
||||||
requestPassword: true,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const element = document.getElementById(
|
|
||||||
ELEMENT_ID_IMPORT_PASSWORD_INPUT
|
|
||||||
);
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView(false);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.performImport(data, undefined);
|
await this.performImport(data);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.performImport(data, undefined);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManageBackups
|
|
||||||
);
|
|
||||||
if (needsPrivilege) {
|
|
||||||
this.application!.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManageBackups,
|
|
||||||
run
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async performImport(data: BackupFile, password?: string) {
|
async performImport(data: BackupFile) {
|
||||||
await this.setState({
|
await this.setState({
|
||||||
importData: {
|
importData: {
|
||||||
...this.getState().importData,
|
...this.getState().importData,
|
||||||
loading: true
|
loading: true,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const result = await this.application!.importData(
|
const result = await this.application.importData(data);
|
||||||
data,
|
|
||||||
password
|
|
||||||
);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
importData: null
|
importData: null,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
} else if ('error' in result) {
|
||||||
|
void alertDialog({
|
||||||
|
text: result.error,
|
||||||
});
|
});
|
||||||
if ('error' in result) {
|
|
||||||
this.application!.alertService!.alert(
|
|
||||||
result.error
|
|
||||||
);
|
|
||||||
} else if (result.errorCount) {
|
} else if (result.errorCount) {
|
||||||
const message = StringImportError(result.errorCount);
|
void alertDialog({
|
||||||
this.application!.alertService!.alert(
|
text: StringImportError(result.errorCount),
|
||||||
message
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.application!.alertService!.alert(
|
void alertDialog({
|
||||||
STRING_IMPORT_SUCCESS
|
text: STRING_IMPORT_SUCCESS,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadDataArchive() {
|
async downloadDataArchive() {
|
||||||
this.application!.getArchiveService().downloadBackup(this.getState().mutable.backupEncrypted);
|
this.application
|
||||||
|
.getArchiveService()
|
||||||
|
.downloadBackup(this.getState().mutable.backupEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
notesAndTagsCount() {
|
notesAndTagsCount() {
|
||||||
return this.application!.getItems(
|
return this.application.getItems([ContentType.Note, ContentType.Tag])
|
||||||
[
|
.length;
|
||||||
ContentType.Note,
|
|
||||||
ContentType.Tag
|
|
||||||
]
|
|
||||||
).length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionStatusForNotes() {
|
encryptionStatusForNotes() {
|
||||||
const length = this.notesAndTagsCount();
|
const length = this.notesAndTagsCount();
|
||||||
return length + "/" + length + " notes and tags encrypted";
|
return length + '/' + length + ' notes and tags encrypted';
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadAutoLockInterval() {
|
async reloadAutoLockInterval() {
|
||||||
const interval = await this.application!.getAutolockService().getAutoLockInterval();
|
const interval = await this.application!.getAutolockService().getAutoLockInterval();
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedAutoLockInterval: interval
|
selectedAutoLockInterval: interval,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectAutoLockInterval(interval: number) {
|
async selectAutoLockInterval(interval: number) {
|
||||||
const run = async () => {
|
if (!(await this.application.authorizeAutolockIntervalChange())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this.application!.getAutolockService().setAutoLockInterval(interval);
|
await this.application!.getAutolockService().setAutoLockInterval(interval);
|
||||||
this.reloadAutoLockInterval();
|
this.reloadAutoLockInterval();
|
||||||
};
|
|
||||||
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManagePasscode
|
|
||||||
);
|
|
||||||
if (needsPrivilege) {
|
|
||||||
this.application!.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManagePasscode,
|
|
||||||
() => {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hidePasswordForm() {
|
hidePasswordForm() {
|
||||||
@@ -521,7 +506,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
showLogin: false,
|
showLogin: false,
|
||||||
showRegister: false,
|
showRegister: false,
|
||||||
user_password: undefined,
|
user_password: undefined,
|
||||||
password_conf: undefined
|
password_conf: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,91 +516,62 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
|
|
||||||
addPasscodeClicked() {
|
addPasscodeClicked() {
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
showPasscodeForm: true
|
showPasscodeForm: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitPasscodeForm() {
|
async submitPasscodeForm() {
|
||||||
const passcode = this.getState().formData.passcode!;
|
const passcode = this.getState().formData.passcode!;
|
||||||
if (passcode !== this.getState().formData.confirmPasscode!) {
|
if (passcode !== this.getState().formData.confirmPasscode!) {
|
||||||
this.application!.alertService!.alert(
|
this.application!.alertService!.alert(STRING_NON_MATCHING_PASSCODES);
|
||||||
STRING_NON_MATCHING_PASSCODES
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE, async () => {
|
await preventRefreshing(
|
||||||
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
||||||
|
async () => {
|
||||||
if (this.application!.hasPasscode()) {
|
if (this.application!.hasPasscode()) {
|
||||||
await this.application!.changePasscode(passcode);
|
await this.application!.changePasscode(passcode);
|
||||||
} else {
|
} else {
|
||||||
await this.application!.setPasscode(passcode);
|
await this.application!.setPasscode(passcode);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
passcode: undefined,
|
passcode: undefined,
|
||||||
confirmPasscode: undefined,
|
confirmPasscode: undefined,
|
||||||
showPasscodeForm: false
|
showPasscodeForm: false,
|
||||||
});
|
});
|
||||||
this.refreshEncryptionStatus();
|
this.refreshEncryptionStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePasscodePressed() {
|
async changePasscodePressed() {
|
||||||
const run = () => {
|
|
||||||
this.getState().formData.changingPasscode = true;
|
this.getState().formData.changingPasscode = true;
|
||||||
this.addPasscodeClicked();
|
this.addPasscodeClicked();
|
||||||
};
|
|
||||||
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManagePasscode
|
|
||||||
);
|
|
||||||
if (needsPrivilege) {
|
|
||||||
this.application!.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManagePasscode,
|
|
||||||
run
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePasscodePressed() {
|
async removePasscodePressed() {
|
||||||
const run = async () => {
|
await preventRefreshing(
|
||||||
const signedIn = this.application!.hasAccount();
|
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
||||||
let message = STRING_REMOVE_PASSCODE_CONFIRMATION;
|
async () => {
|
||||||
if (!signedIn) {
|
if (await this.application!.removePasscode()) {
|
||||||
message += STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM;
|
await this.application
|
||||||
}
|
.getAutolockService()
|
||||||
if (await confirmDialog({
|
.deleteAutolockPreference();
|
||||||
text: message,
|
|
||||||
confirmButtonStyle: 'danger'
|
|
||||||
})) {
|
|
||||||
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => {
|
|
||||||
await this.application.getAutolockService().deleteAutolockPreference();
|
|
||||||
await this.application!.removePasscode();
|
|
||||||
await this.reloadAutoLockInterval();
|
await this.reloadAutoLockInterval();
|
||||||
});
|
|
||||||
this.refreshEncryptionStatus();
|
this.refreshEncryptionStatus();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManagePasscode
|
|
||||||
);
|
|
||||||
if (needsPrivilege) {
|
|
||||||
this.application!.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManagePasscode,
|
|
||||||
run
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
openErrorReportingDialog() {
|
openErrorReportingDialog() {
|
||||||
alertDialog({
|
alertDialog({
|
||||||
title: 'Data sent during automatic error reporting',
|
title: 'Data sent during automatic error reporting',
|
||||||
text: `
|
text: `
|
||||||
We use <a target="_blank" href="https://www.bugsnag.com/">Bugsnag</a>
|
We use <a target="_blank" rel="noreferrer" href="https://www.bugsnag.com/">Bugsnag</a>
|
||||||
to automatically report errors that occur while the app is running. See
|
to automatically report errors that occur while the app is running. See
|
||||||
<a target="_blank" href="https://docs.bugsnag.com/platforms/javascript/#sending-diagnostic-data">
|
<a target="_blank" rel="noreferrer" href="https://docs.bugsnag.com/platforms/javascript/#sending-diagnostic-data">
|
||||||
this article, paragraph 'Browser' under 'Sending diagnostic data',
|
this article, paragraph 'Browser' under 'Sending diagnostic data',
|
||||||
</a>
|
</a>
|
||||||
to see what data is included in error reports.
|
to see what data is included in error reports.
|
||||||
@@ -624,15 +580,15 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
|||||||
anonymized. We use error reports to be alerted when something in our
|
anonymized. We use error reports to be alerted when something in our
|
||||||
code is causing unexpected errors and crashes in your application
|
code is causing unexpected errors and crashes in your application
|
||||||
experience.
|
experience.
|
||||||
`
|
`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleErrorReportingEnabled() {
|
toggleErrorReportingEnabled() {
|
||||||
if (this.state.errorReportingEnabled) {
|
if (this.state.errorReportingEnabled) {
|
||||||
storage.set(StorageKey.DisableErrorReporting, true);
|
disableErrorReporting();
|
||||||
} else {
|
} else {
|
||||||
storage.set(StorageKey.DisableErrorReporting, false);
|
enableErrorReporting();
|
||||||
}
|
}
|
||||||
if (!this.state.syncInProgress) {
|
if (!this.state.syncInProgress) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
@@ -654,7 +610,7 @@ export class AccountMenu extends WebDirective {
|
|||||||
this.bindToController = true;
|
this.bindToController = true;
|
||||||
this.scope = {
|
this.scope = {
|
||||||
closeFunction: '&',
|
closeFunction: '&',
|
||||||
application: '='
|
application: '=',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type ActionsMenuState = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionsMenuCtrl extends PureViewCtrl<{}, ActionsMenuState> implements ActionsMenuScope {
|
class ActionsMenuCtrl extends PureViewCtrl<unknown, ActionsMenuState> implements ActionsMenuScope {
|
||||||
application!: WebApplication
|
application!: WebApplication
|
||||||
item!: SNItem
|
item!: SNItem
|
||||||
private removeHiddenExtensionsListener?: IReactionDisposer;
|
private removeHiddenExtensionsListener?: IReactionDisposer;
|
||||||
@@ -63,7 +63,7 @@ class ActionsMenuCtrl extends PureViewCtrl<{}, ActionsMenuState> implements Acti
|
|||||||
hiddenExtensions: this.appState.actionsMenu.hiddenExtensions
|
hiddenExtensions: this.appState.actionsMenu.hiddenExtensions
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.removeHiddenExtensionsListener?.();
|
this.removeHiddenExtensionsListener?.();
|
||||||
@@ -74,7 +74,7 @@ class ActionsMenuCtrl extends PureViewCtrl<{}, ActionsMenuState> implements Acti
|
|||||||
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => {
|
const extensions = this.application.actionsManager!.getExtensions().sort((a, b) => {
|
||||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||||
});
|
});
|
||||||
let extensionsState: Record<UuidString, ExtensionState> = {};
|
const extensionsState: Record<UuidString, ExtensionState> = {};
|
||||||
extensions.map((extension) => {
|
extensions.map((extension) => {
|
||||||
extensionsState[extension.uuid] = {
|
extensionsState[extension.uuid] = {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -114,7 +114,7 @@ class ActionsMenuCtrl extends PureViewCtrl<{}, ActionsMenuState> implements Acti
|
|||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
subrows: this.subRowsForAction(action, extension)
|
subrows: this.subRowsForAction(action, extension)
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class ComponentViewCtrl implements ComponentViewScope {
|
|||||||
this.$timeout(() => {
|
this.$timeout(() => {
|
||||||
this.reloading = false;
|
this.reloading = false;
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onVisibilityChange() {
|
private onVisibilityChange() {
|
||||||
@@ -228,6 +228,7 @@ class ComponentViewCtrl implements ComponentViewScope {
|
|||||||
if (!iframe.contentWindow!.origin || iframe.contentWindow!.origin === 'null') {
|
if (!iframe.contentWindow!.origin || iframe.contentWindow!.origin === 'null') {
|
||||||
desktopError = true;
|
desktopError = true;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
this.$timeout.cancel(this.loadTimeout);
|
this.$timeout.cancel(this.loadTimeout);
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
|
|||||||
editors: editors,
|
editors: editors,
|
||||||
defaultEditor: defaultEditor
|
defaultEditor: defaultEditor
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
selectComponent(component: SNComponent) {
|
selectComponent(component: SNComponent) {
|
||||||
if (component) {
|
if (component) {
|
||||||
if (component.conflictOf) {
|
if (component.conflictOf) {
|
||||||
this.application.changeAndSaveItem(component.uuid, (mutator) => {
|
this.application.changeAndSaveItem(component.uuid, (mutator) => {
|
||||||
mutator.conflictOf = undefined;
|
mutator.conflictOf = undefined;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.$timeout(() => {
|
this.$timeout(() => {
|
||||||
@@ -87,7 +87,7 @@ class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
|
|||||||
this.application.changeItem(currentDefault.uuid, (m) => {
|
this.application.changeItem(currentDefault.uuid, (m) => {
|
||||||
const mutator = m as ComponentMutator;
|
const mutator = m as ComponentMutator;
|
||||||
mutator.defaultEditor = false;
|
mutator.defaultEditor = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
this.application.changeAndSaveItem(component.uuid, (m) => {
|
this.application.changeAndSaveItem(component.uuid, (m) => {
|
||||||
const mutator = m as ComponentMutator;
|
const mutator = m as ComponentMutator;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface HistoryScope {
|
|||||||
item: SNItem
|
item: SNItem
|
||||||
}
|
}
|
||||||
|
|
||||||
class HistoryMenuCtrl extends PureViewCtrl<{}, HistoryState> implements HistoryScope {
|
class HistoryMenuCtrl extends PureViewCtrl<unknown, HistoryState> implements HistoryScope {
|
||||||
|
|
||||||
diskEnabled = false
|
diskEnabled = false
|
||||||
autoOptimize = false
|
autoOptimize = false
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ export { MenuRow } from './menuRow';
|
|||||||
export { PanelResizer } from './panelResizer';
|
export { PanelResizer } from './panelResizer';
|
||||||
export { PasswordWizard } from './passwordWizard';
|
export { PasswordWizard } from './passwordWizard';
|
||||||
export { PermissionsModal } from './permissionsModal';
|
export { PermissionsModal } from './permissionsModal';
|
||||||
export { PrivilegesAuthModal } from './privilegesAuthModal';
|
|
||||||
export { PrivilegesManagementModal } from './privilegesManagementModal';
|
|
||||||
export { RevisionPreviewModal } from './revisionPreviewModal';
|
export { RevisionPreviewModal } from './revisionPreviewModal';
|
||||||
export { HistoryMenu } from './historyMenu';
|
export { HistoryMenu } from './historyMenu';
|
||||||
export { SyncResolutionMenu } from './syncResolutionMenu';
|
export { SyncResolutionMenu } from './syncResolutionMenu';
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { debounce } from '@/utils';
|
|||||||
enum PanelSide {
|
enum PanelSide {
|
||||||
Right = 'right',
|
Right = 'right',
|
||||||
Left = 'left'
|
Left = 'left'
|
||||||
};
|
}
|
||||||
enum MouseEventType {
|
enum MouseEventType {
|
||||||
Move = 'mousemove',
|
Move = 'mousemove',
|
||||||
Down = 'mousedown',
|
Down = 'mousedown',
|
||||||
Up = 'mouseup'
|
Up = 'mouseup'
|
||||||
};
|
}
|
||||||
enum CssClass {
|
enum CssClass {
|
||||||
Hoverable = 'hoverable',
|
Hoverable = 'hoverable',
|
||||||
AlwaysVisible = 'always-visible',
|
AlwaysVisible = 'always-visible',
|
||||||
@@ -19,7 +19,7 @@ enum CssClass {
|
|||||||
NoSelection = 'no-selection',
|
NoSelection = 'no-selection',
|
||||||
Collapsed = 'collapsed',
|
Collapsed = 'collapsed',
|
||||||
AnimateOpacity = 'animate-opacity',
|
AnimateOpacity = 'animate-opacity',
|
||||||
};
|
}
|
||||||
const WINDOW_EVENT_RESIZE = 'resize';
|
const WINDOW_EVENT_RESIZE = 'resize';
|
||||||
|
|
||||||
type ResizeFinishCallback = (
|
type ResizeFinishCallback = (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const DEFAULT_CONTINUE_TITLE = "Continue";
|
|||||||
enum Steps {
|
enum Steps {
|
||||||
PasswordStep = 1,
|
PasswordStep = 1,
|
||||||
FinishStep = 2
|
FinishStep = 2
|
||||||
};
|
}
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
currentPassword?: string,
|
currentPassword?: string,
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
import { WebDirective } from './../../types';
|
|
||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import { ProtectedAction, PrivilegeCredential, PrivilegeSessionLength } from '@standardnotes/snjs';
|
|
||||||
import template from '%/directives/privileges-auth-modal.pug';
|
|
||||||
|
|
||||||
type PrivilegesAuthModalScope = {
|
|
||||||
application: WebApplication
|
|
||||||
action: ProtectedAction
|
|
||||||
onSuccess: () => void
|
|
||||||
onCancel: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrivilegesAuthModalCtrl implements PrivilegesAuthModalScope {
|
|
||||||
$element: JQLite
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
application!: WebApplication
|
|
||||||
action!: ProtectedAction
|
|
||||||
onSuccess!: () => void
|
|
||||||
onCancel!: () => void
|
|
||||||
authParameters: Partial<Record<PrivilegeCredential, string>> = {}
|
|
||||||
sessionLengthOptions!: { value: PrivilegeSessionLength, label: string }[]
|
|
||||||
selectedSessionLength!: PrivilegeSessionLength
|
|
||||||
requiredCredentials!: PrivilegeCredential[]
|
|
||||||
failedCredentials!: PrivilegeCredential[]
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor(
|
|
||||||
$element: JQLite,
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
) {
|
|
||||||
this.$element = $element;
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
this.sessionLengthOptions = this.application!.privilegesService!
|
|
||||||
.getSessionLengthOptions();
|
|
||||||
this.application.privilegesService!.getSelectedSessionLength()
|
|
||||||
.then((length) => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.selectedSessionLength = length;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.application.privilegesService!.netCredentialsForAction(this.action)
|
|
||||||
.then((credentials) => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.requiredCredentials = credentials.sort();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
selectSessionLength(length: PrivilegeSessionLength) {
|
|
||||||
this.selectedSessionLength = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
promptForCredential(credential: PrivilegeCredential) {
|
|
||||||
return this.application.privilegesService!.displayInfoForCredential(credential).prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
this.dismiss();
|
|
||||||
this.onCancel && this.onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
isCredentialInFailureState(credential: PrivilegeCredential) {
|
|
||||||
if (!this.failedCredentials) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.failedCredentials.find((candidate) => {
|
|
||||||
return candidate === credential;
|
|
||||||
}) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
const failed = [];
|
|
||||||
for (const cred of this.requiredCredentials) {
|
|
||||||
const value = this.authParameters[cred];
|
|
||||||
if (!value || value.length === 0) {
|
|
||||||
failed.push(cred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.failedCredentials = failed;
|
|
||||||
return failed.length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
if (!this.validate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const result = await this.application.privilegesService!.authenticateAction(
|
|
||||||
this.action,
|
|
||||||
this.authParameters
|
|
||||||
);
|
|
||||||
this.$timeout(() => {
|
|
||||||
if (result.success) {
|
|
||||||
this.application.privilegesService!.setSessionLength(this.selectedSessionLength);
|
|
||||||
this.onSuccess();
|
|
||||||
this.dismiss();
|
|
||||||
} else {
|
|
||||||
this.failedCredentials = result.failedCredentials;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss() {
|
|
||||||
const elem = this.$element;
|
|
||||||
const scope = elem.scope();
|
|
||||||
scope.$destroy();
|
|
||||||
elem.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PrivilegesAuthModal extends WebDirective {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.restrict = 'E';
|
|
||||||
this.template = template;
|
|
||||||
this.controller = PrivilegesAuthModalCtrl;
|
|
||||||
this.controllerAs = 'ctrl';
|
|
||||||
this.bindToController = true;
|
|
||||||
this.scope = {
|
|
||||||
action: '=',
|
|
||||||
onSuccess: '=',
|
|
||||||
onCancel: '=',
|
|
||||||
application: '='
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { WebDirective } from './../../types';
|
|
||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import template from '%/directives/privileges-management-modal.pug';
|
|
||||||
import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from '@standardnotes/snjs';
|
|
||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
|
||||||
import { PrivilegeMutator } from '@standardnotes/snjs';
|
|
||||||
|
|
||||||
type DisplayInfo = {
|
|
||||||
label: string
|
|
||||||
prompt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrivilegesManagementModalCtrl extends PureViewCtrl {
|
|
||||||
|
|
||||||
hasPasscode = false
|
|
||||||
hasAccount = false
|
|
||||||
$element: JQLite
|
|
||||||
application!: WebApplication
|
|
||||||
privileges!: SNPrivileges
|
|
||||||
availableActions!: ProtectedAction[]
|
|
||||||
availableCredentials!: PrivilegeCredential[]
|
|
||||||
sessionExpirey!: string
|
|
||||||
sessionExpired = true
|
|
||||||
credentialDisplayInfo: Partial<Record<PrivilegeCredential, DisplayInfo>> = {}
|
|
||||||
onCancel!: () => void
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor(
|
|
||||||
$timeout: ng.ITimeoutService,
|
|
||||||
$element: JQLite
|
|
||||||
) {
|
|
||||||
super($timeout);
|
|
||||||
this.$element = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAppLaunch() {
|
|
||||||
super.onAppLaunch();
|
|
||||||
this.hasPasscode = this.application.hasPasscode();
|
|
||||||
this.hasAccount = !this.application.noAccount();
|
|
||||||
this.reloadPrivileges();
|
|
||||||
}
|
|
||||||
|
|
||||||
displayInfoForCredential(credential: PrivilegeCredential) {
|
|
||||||
const info: any = this.application.privilegesService!.displayInfoForCredential(credential);
|
|
||||||
if (credential === PrivilegeCredential.LocalPasscode) {
|
|
||||||
info.availability = this.hasPasscode;
|
|
||||||
} else if (credential === PrivilegeCredential.AccountPassword) {
|
|
||||||
info.availability = this.hasAccount;
|
|
||||||
} else {
|
|
||||||
info.availability = true;
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayInfoForAction(action: ProtectedAction) {
|
|
||||||
return this.application.privilegesService!.displayInfoForAction(action).label;
|
|
||||||
}
|
|
||||||
|
|
||||||
isCredentialRequiredForAction(action: ProtectedAction, credential: PrivilegeCredential) {
|
|
||||||
if (!this.privileges) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.privileges.isCredentialRequiredForAction(action, credential);
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearSession() {
|
|
||||||
await this.application.privilegesService!.clearSession();
|
|
||||||
this.reloadPrivileges();
|
|
||||||
}
|
|
||||||
|
|
||||||
async reloadPrivileges() {
|
|
||||||
this.availableActions = this.application.privilegesService!.getAvailableActions();
|
|
||||||
this.availableCredentials = this.application.privilegesService!.getAvailableCredentials();
|
|
||||||
const sessionEndDate = await this.application.privilegesService!.getSessionExpirey();
|
|
||||||
this.sessionExpirey = sessionEndDate.toLocaleString();
|
|
||||||
this.sessionExpired = new Date() >= sessionEndDate;
|
|
||||||
for (const cred of this.availableCredentials) {
|
|
||||||
this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred);
|
|
||||||
}
|
|
||||||
const privs = await this.application.privilegesService!.getPrivileges();
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.privileges = privs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkboxValueChanged(action: ProtectedAction, credential: PrivilegeCredential) {
|
|
||||||
this.application.changeAndSaveItem(this.privileges.uuid, (m) => {
|
|
||||||
const mutator = m as PrivilegeMutator;
|
|
||||||
mutator.toggleCredentialForAction(action, credential);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
this.dismiss();
|
|
||||||
this.onCancel && this.onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss() {
|
|
||||||
const elem = this.$element;
|
|
||||||
const scope = elem.scope();
|
|
||||||
scope.$destroy();
|
|
||||||
elem.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PrivilegesManagementModal extends WebDirective {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.restrict = 'E';
|
|
||||||
this.template = template;
|
|
||||||
this.controller = PrivilegesManagementModalCtrl;
|
|
||||||
this.controllerAs = 'ctrl';
|
|
||||||
this.bindToController = true;
|
|
||||||
this.scope = {
|
|
||||||
application: '='
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,5 +15,4 @@ import '../../../vendor/assets/javascripts/zip/zip';
|
|||||||
import '../../../vendor/assets/javascripts/zip/z-worker';
|
import '../../../vendor/assets/javascripts/zip/z-worker';
|
||||||
|
|
||||||
// entry point
|
// entry point
|
||||||
// eslint-disable-next-line import/first
|
|
||||||
import './app';
|
import './app';
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { EncryptionIntent, ProtectedAction, ContentType, SNNote, BackupFile, PayloadContent } from '@standardnotes/snjs';
|
import {
|
||||||
|
EncryptionIntent,
|
||||||
|
ContentType,
|
||||||
|
SNNote,
|
||||||
|
BackupFile,
|
||||||
|
PayloadContent,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
|
|
||||||
function zippableTxtName(name: string, suffix = ""): string {
|
function zippableTxtName(name: string, suffix = ""): string {
|
||||||
const sanitizedName = name
|
const sanitizedName = name
|
||||||
@@ -22,12 +28,11 @@ export class ArchiveManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async downloadBackup(encrypted: boolean) {
|
public async downloadBackup(encrypted: boolean) {
|
||||||
const run = async () => {
|
|
||||||
const intent = encrypted
|
const intent = encrypted
|
||||||
? EncryptionIntent.FileEncrypted
|
? EncryptionIntent.FileEncrypted
|
||||||
: EncryptionIntent.FileDecrypted;
|
: EncryptionIntent.FileDecrypted;
|
||||||
|
|
||||||
const data = await this.application.createBackupFile(intent);
|
const data = await this.application.createBackupFile(intent, true);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,20 +52,6 @@ export class ArchiveManager {
|
|||||||
/** download as zipped plain text files */
|
/** download as zipped plain text files */
|
||||||
this.downloadZippedDecryptedItems(data);
|
this.downloadZippedDecryptedItems(data);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
await this.application.privilegesService!
|
|
||||||
.actionRequiresPrivilege(ProtectedAction.ManageBackups)
|
|
||||||
) {
|
|
||||||
this.application.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManageBackups,
|
|
||||||
() => {
|
|
||||||
run();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private formattedDate() {
|
private formattedDate() {
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ export class BrowserBridge implements Bridge {
|
|||||||
|
|
||||||
/** No-ops */
|
/** No-ops */
|
||||||
|
|
||||||
syncComponents() {}
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
onMajorDataChange() {}
|
syncComponents(): void {}
|
||||||
onInitialDataLoad() {}
|
onMajorDataChange(): void {}
|
||||||
onSearch() {}
|
onInitialDataLoad(): void {}
|
||||||
downloadBackup() {}
|
onSearch(): void {}
|
||||||
|
downloadBackup(): void {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { SNComponent, PurePayload, ComponentMutator, AppDataField, ContentType } from '@standardnotes/snjs';
|
import {
|
||||||
|
SNComponent,
|
||||||
|
PurePayload,
|
||||||
|
ComponentMutator,
|
||||||
|
AppDataField,
|
||||||
|
EncryptionIntent,
|
||||||
|
ApplicationService,
|
||||||
|
ApplicationEvent,
|
||||||
|
removeFromArray,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
// An interface used by the Desktop app to interact with SN
|
// An interface used by the Desktop app to interact with SN
|
||||||
import { isDesktopApplication } from '@/utils';
|
import { isDesktopApplication } from '@/utils';
|
||||||
import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from '@standardnotes/snjs';
|
|
||||||
import { Bridge } from './bridge';
|
import { Bridge } from './bridge';
|
||||||
|
|
||||||
type UpdateObserverCallback = (component: SNComponent) => void
|
type UpdateObserverCallback = (component: SNComponent) => void
|
||||||
@@ -67,7 +75,7 @@ export class DesktopManager extends ApplicationService {
|
|||||||
|
|
||||||
getExtServerHost() {
|
getExtServerHost() {
|
||||||
console.assert(
|
console.assert(
|
||||||
this.bridge.extensionsServerHost,
|
!!this.bridge.extensionsServerHost,
|
||||||
'extServerHost is null'
|
'extServerHost is null'
|
||||||
);
|
);
|
||||||
return this.bridge.extensionsServerHost;
|
return this.bridge.extensionsServerHost;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { isNullOrUndefined, SNLog } from '@standardnotes/snjs';
|
|||||||
import { isDesktopApplication, isDev } from '@/utils';
|
import { isDesktopApplication, isDev } from '@/utils';
|
||||||
import { storage, StorageKey } from './localStorage';
|
import { storage, StorageKey } from './localStorage';
|
||||||
import Bugsnag from '@bugsnag/js';
|
import Bugsnag from '@bugsnag/js';
|
||||||
|
import { WebCrypto } from '../crypto';
|
||||||
|
|
||||||
declare const __VERSION__: string;
|
declare const __VERSION__: string;
|
||||||
declare global {
|
declare global {
|
||||||
@@ -21,7 +22,7 @@ function redactFilePath(line: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startErrorReporting() {
|
export function startErrorReporting(): void {
|
||||||
const disableErrorReporting = storage.get(StorageKey.DisableErrorReporting);
|
const disableErrorReporting = storage.get(StorageKey.DisableErrorReporting);
|
||||||
if (
|
if (
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +38,15 @@ export function startErrorReporting() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const storedUserId = storage.get(StorageKey.AnonymousUserId);
|
||||||
|
let anonymousUserId: string;
|
||||||
|
if (storedUserId === null) {
|
||||||
|
anonymousUserId = WebCrypto.generateUUIDSync();
|
||||||
|
storage.set(StorageKey.AnonymousUserId, anonymousUserId);
|
||||||
|
} else {
|
||||||
|
anonymousUserId = storedUserId;
|
||||||
|
}
|
||||||
|
|
||||||
Bugsnag.start({
|
Bugsnag.start({
|
||||||
apiKey: window._bugsnag_api_key,
|
apiKey: window._bugsnag_api_key,
|
||||||
appType: isDesktopApplication() ? 'desktop' : 'web',
|
appType: isDesktopApplication() ? 'desktop' : 'web',
|
||||||
@@ -46,6 +56,8 @@ export function startErrorReporting() {
|
|||||||
releaseStage: isDev ? 'development' : undefined,
|
releaseStage: isDev ? 'development' : undefined,
|
||||||
enabledBreadcrumbTypes: ['error', 'log'],
|
enabledBreadcrumbTypes: ['error', 'log'],
|
||||||
onError(event) {
|
onError(event) {
|
||||||
|
event.setUser(anonymousUserId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redact any data that could be used to identify user,
|
* Redact any data that could be used to identify user,
|
||||||
* such as file paths.
|
* such as file paths.
|
||||||
@@ -95,3 +107,16 @@ export function startErrorReporting() {
|
|||||||
SNLog.onError = console.error;
|
SNLog.onError = console.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function disableErrorReporting() {
|
||||||
|
storage.remove(StorageKey.AnonymousUserId);
|
||||||
|
storage.set(StorageKey.DisableErrorReporting, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enableErrorReporting() {
|
||||||
|
storage.set(StorageKey.DisableErrorReporting, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function errorReportingId() {
|
||||||
|
return storage.get(StorageKey.AnonymousUserId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export enum KeyboardKey {
|
|||||||
Backspace = "Backspace",
|
Backspace = "Backspace",
|
||||||
Up = "ArrowUp",
|
Up = "ArrowUp",
|
||||||
Down = "ArrowDown",
|
Down = "ArrowDown",
|
||||||
};
|
}
|
||||||
|
|
||||||
export enum KeyboardModifier {
|
export enum KeyboardModifier {
|
||||||
Shift = "Shift",
|
Shift = "Shift",
|
||||||
@@ -12,12 +12,12 @@ export enum KeyboardModifier {
|
|||||||
/** ⌘ key on Mac, ⊞ key on Windows */
|
/** ⌘ key on Mac, ⊞ key on Windows */
|
||||||
Meta = "Meta",
|
Meta = "Meta",
|
||||||
Alt = "Alt",
|
Alt = "Alt",
|
||||||
};
|
}
|
||||||
|
|
||||||
enum KeyboardKeyEvent {
|
enum KeyboardKeyEvent {
|
||||||
Down = "KeyEventDown",
|
Down = "KeyEventDown",
|
||||||
Up = "KeyEventUp"
|
Up = "KeyEventUp"
|
||||||
};
|
}
|
||||||
|
|
||||||
type KeyboardObserver = {
|
type KeyboardObserver = {
|
||||||
key?: KeyboardKey | string
|
key?: KeyboardKey | string
|
||||||
@@ -39,10 +39,10 @@ export class KeyboardManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.handleKeyDown = (event: KeyboardEvent) => {
|
this.handleKeyDown = (event: KeyboardEvent) => {
|
||||||
this.notifyObserver(event, KeyboardKeyEvent.Down);
|
this.notifyObserver(event, KeyboardKeyEvent.Down);
|
||||||
}
|
};
|
||||||
this.handleKeyUp = (event: KeyboardEvent) => {
|
this.handleKeyUp = (event: KeyboardEvent) => {
|
||||||
this.notifyObserver(event, KeyboardKeyEvent.Up);
|
this.notifyObserver(event, KeyboardKeyEvent.Up);
|
||||||
}
|
};
|
||||||
window.addEventListener('keydown', this.handleKeyDown);
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
window.addEventListener('keyup', this.handleKeyUp);
|
window.addEventListener('keyup', this.handleKeyUp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
DisableErrorReporting = 'DisableErrorReporting',
|
DisableErrorReporting = 'DisableErrorReporting',
|
||||||
|
AnonymousUserId = 'AnonymousUserId',
|
||||||
|
ShowBetaWarning = 'ShowBetaWarning',
|
||||||
|
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StorageValue = {
|
export type StorageValue = {
|
||||||
[StorageKey.DisableErrorReporting]: boolean;
|
[StorageKey.DisableErrorReporting]: boolean;
|
||||||
|
[StorageKey.AnonymousUserId]: string;
|
||||||
|
[StorageKey.ShowBetaWarning]: boolean;
|
||||||
|
[StorageKey.ShowNoAccountWarning]: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = {
|
export const storage = {
|
||||||
@@ -11,10 +17,10 @@ export const storage = {
|
|||||||
const value = localStorage.getItem(key);
|
const value = localStorage.getItem(key);
|
||||||
return value ? JSON.parse(value) : null;
|
return value ? JSON.parse(value) : null;
|
||||||
},
|
},
|
||||||
set<K extends StorageKey>(key: K, value: StorageValue[K]) {
|
set<K extends StorageKey>(key: K, value: StorageValue[K]): void {
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
},
|
},
|
||||||
remove(key: StorageKey) {
|
remove(key: StorageKey): void {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
ComponentMutator,
|
ComponentMutator,
|
||||||
Copy,
|
Copy,
|
||||||
dictToArray
|
dictToArray
|
||||||
} from '@standardnotes/snjs';
|
, PayloadContent , ComponentPermission } from '@standardnotes/snjs';
|
||||||
import { PayloadContent } from '@standardnotes/snjs';
|
|
||||||
import { ComponentPermission } from '@standardnotes/snjs';
|
|
||||||
|
|
||||||
/** A class for handling installation of system extensions */
|
/** A class for handling installation of system extensions */
|
||||||
export class NativeExtManager extends ApplicationService {
|
export class NativeExtManager extends ApplicationService {
|
||||||
@@ -82,7 +82,7 @@ export class NativeExtManager extends ApplicationService {
|
|||||||
// Handle addition of SN|ExtensionRepo permission
|
// Handle addition of SN|ExtensionRepo permission
|
||||||
const permissions = Copy(extensionsManager!.permissions) as ComponentPermission[];
|
const permissions = Copy(extensionsManager!.permissions) as ComponentPermission[];
|
||||||
const permission = permissions.find((p) => {
|
const permission = permissions.find((p) => {
|
||||||
return p.name === ComponentAction.StreamItems
|
return p.name === ComponentAction.StreamItems;
|
||||||
});
|
});
|
||||||
if (permission && !permission.content_types!.includes(ContentType.ExtensionRepo)) {
|
if (permission && !permission.content_types!.includes(ContentType.ExtensionRepo)) {
|
||||||
permission.content_types!.push(ContentType.ExtensionRepo);
|
permission.content_types!.push(ContentType.ExtensionRepo);
|
||||||
@@ -160,7 +160,7 @@ export class NativeExtManager extends ApplicationService {
|
|||||||
// Handle addition of SN|ExtensionRepo permission
|
// Handle addition of SN|ExtensionRepo permission
|
||||||
const permissions = Copy(batchManager!.permissions) as ComponentPermission[];
|
const permissions = Copy(batchManager!.permissions) as ComponentPermission[];
|
||||||
const permission = permissions.find((p) => {
|
const permission = permissions.find((p) => {
|
||||||
return p.name === ComponentAction.StreamItems
|
return p.name === ComponentAction.StreamItems;
|
||||||
});
|
});
|
||||||
if (permission && !permission.content_types!.includes(ContentType.ExtensionRepo)) {
|
if (permission && !permission.content_types!.includes(ContentType.ExtensionRepo)) {
|
||||||
permission.content_types!.push(ContentType.ExtensionRepo);
|
permission.content_types!.push(ContentType.ExtensionRepo);
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ export class ThemeManager extends ApplicationService {
|
|||||||
this.deactivateAllThemes();
|
this.deactivateAllThemes();
|
||||||
} else if (event === ApplicationEvent.StorageReady) {
|
} else if (event === ApplicationEvent.StorageReady) {
|
||||||
await this.activateCachedThemes();
|
await this.activateCachedThemes();
|
||||||
if (!this.webApplication.getDesktopService().isDesktop) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +73,7 @@ export class ThemeManager extends ApplicationService {
|
|||||||
this.deactivateTheme(theme.uuid);
|
this.deactivateTheme(theme.uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearAppThemeState() {
|
private clearAppThemeState() {
|
||||||
|
|||||||
@@ -1,27 +1,45 @@
|
|||||||
|
import { Platform, SNApplication } from '@standardnotes/snjs';
|
||||||
|
import { getPlatform, isDesktopApplication } from './utils';
|
||||||
|
|
||||||
/** @generic */
|
/** @generic */
|
||||||
export const STRING_SESSION_EXPIRED = "Your session has expired. New changes will not be pulled in. Please sign in to refresh your session.";
|
export const STRING_SESSION_EXPIRED =
|
||||||
export const STRING_DEFAULT_FILE_ERROR = "Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.";
|
'Your session has expired. New changes will not be pulled in. Please sign in to refresh your session.';
|
||||||
export const STRING_GENERIC_SYNC_ERROR = "There was an error syncing. Please try again. If all else fails, try signing out and signing back in.";
|
export const STRING_DEFAULT_FILE_ERROR =
|
||||||
|
'Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.';
|
||||||
|
export const STRING_GENERIC_SYNC_ERROR =
|
||||||
|
'There was an error syncing. Please try again. If all else fails, try signing out and signing back in.';
|
||||||
export function StringSyncException(data: any) {
|
export function StringSyncException(data: any) {
|
||||||
return `There was an error while trying to save your items. Please contact support and share this message: ${JSON.stringify(data)}.`;
|
return `There was an error while trying to save your items. Please contact support and share this message: ${JSON.stringify(
|
||||||
|
data
|
||||||
|
)}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @footer */
|
/** @footer */
|
||||||
export const STRING_NEW_UPDATE_READY = "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation.";
|
export const STRING_NEW_UPDATE_READY =
|
||||||
|
"A new update is ready to install. Please use the top-level 'Updates' menu to manage installation.";
|
||||||
|
|
||||||
/** @tags */
|
/** @tags */
|
||||||
export const STRING_DELETE_TAG = "Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.";
|
export const STRING_DELETE_TAG =
|
||||||
|
'Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.';
|
||||||
|
|
||||||
/** @editor */
|
/** @editor */
|
||||||
export const STRING_SAVING_WHILE_DOCUMENT_HIDDEN = 'Attempting to save an item while the application is hidden. To protect data integrity, please refresh the application window and try again.'
|
export const STRING_SAVING_WHILE_DOCUMENT_HIDDEN =
|
||||||
export const STRING_DELETED_NOTE = "The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded.";
|
'Attempting to save an item while the application is hidden. To protect data integrity, please refresh the application window and try again.';
|
||||||
export const STRING_INVALID_NOTE = "The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.";
|
export const STRING_DELETED_NOTE =
|
||||||
export const STRING_ELLIPSES = "...";
|
'The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded.';
|
||||||
export const STRING_GENERIC_SAVE_ERROR = "There was an error saving your note. Please try again.";
|
export const STRING_INVALID_NOTE =
|
||||||
export const STRING_DELETE_PLACEHOLDER_ATTEMPT = "This note is a placeholder and cannot be deleted. To remove from your list, simply navigate to a different note.";
|
"The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.";
|
||||||
export const STRING_ARCHIVE_LOCKED_ATTEMPT = "This note is locked. If you'd like to archive it, unlock it, and try again.";
|
export const STRING_ELLIPSES = '...';
|
||||||
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_GENERIC_SAVE_ERROR =
|
||||||
export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again.";
|
'There was an error saving your note. Please try again.';
|
||||||
|
export const STRING_DELETE_PLACEHOLDER_ATTEMPT =
|
||||||
|
'This note is a placeholder and cannot be deleted. To remove from your list, simply navigate to a different note.';
|
||||||
|
export const STRING_ARCHIVE_LOCKED_ATTEMPT =
|
||||||
|
"This note is locked. If you'd like to archive it, unlock it, and try again.";
|
||||||
|
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 function StringDeleteNote(title: string, permanently: boolean) {
|
export function StringDeleteNote(title: string, permanently: boolean) {
|
||||||
return permanently
|
return permanently
|
||||||
? `Are you sure you want to permanently delete ${title}?`
|
? `Are you sure you want to permanently delete ${title}?`
|
||||||
@@ -32,44 +50,80 @@ export function StringEmptyTrash(count: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @account */
|
/** @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_ACCOUNT_MENU_UNCHECK_MERGE =
|
||||||
export const STRING_SIGN_OUT_CONFIRMATION = "Are you sure you want to end your session? This will delete all local items and extensions.";
|
'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_ERROR_DECRYPTING_IMPORT = "There was an error decrypting your items. Make sure the password you entered is correct and try again.";
|
export const STRING_SIGN_OUT_CONFIRMATION =
|
||||||
export const STRING_E2E_ENABLED = "End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.";
|
'Are you sure you want to end your session? This will delete all local items and extensions.';
|
||||||
export const STRING_LOCAL_ENC_ENABLED = "Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.";
|
export const STRING_ERROR_DECRYPTING_IMPORT =
|
||||||
export const STRING_ENC_NOT_ENABLED = "Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.";
|
'There was an error decrypting your items. Make sure the password you entered is correct and try again.';
|
||||||
export const STRING_IMPORT_SUCCESS = "Your data has been successfully imported.";
|
export const STRING_E2E_ENABLED =
|
||||||
export const STRING_REMOVE_PASSCODE_CONFIRMATION = "Are you sure you want to remove your application passcode?";
|
'End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.';
|
||||||
export const STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM = " This will remove encryption from your local data.";
|
export const STRING_LOCAL_ENC_ENABLED =
|
||||||
export const STRING_NON_MATCHING_PASSCODES = "The two passcodes you entered do not match. Please try again.";
|
'Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.';
|
||||||
export const STRING_NON_MATCHING_PASSWORDS = "The two passwords you entered do not match. Please try again.";
|
export const STRING_ENC_NOT_ENABLED =
|
||||||
export const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys...";
|
'Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.';
|
||||||
export const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys...";
|
export const STRING_IMPORT_SUCCESS =
|
||||||
export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again.";
|
'Your data has been successfully imported.';
|
||||||
|
export const STRING_REMOVE_PASSCODE_CONFIRMATION =
|
||||||
|
'Are you sure you want to remove your application passcode?';
|
||||||
|
export const STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM =
|
||||||
|
' This will remove encryption from your local data.';
|
||||||
|
export const STRING_NON_MATCHING_PASSCODES =
|
||||||
|
'The two passcodes you entered do not match. Please try again.';
|
||||||
|
export const STRING_NON_MATCHING_PASSWORDS =
|
||||||
|
'The two passwords you entered do not match. Please try again.';
|
||||||
|
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 function StringImportError(errorCount: number) {
|
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.`;
|
return `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`;
|
||||||
}
|
}
|
||||||
export const STRING_UNSUPPORTED_BACKUP_FILE_VERSION = 'This backup file was created using an unsupported version of the application and cannot be imported here. Please update your application and try again.';
|
export const STRING_UNSUPPORTED_BACKUP_FILE_VERSION =
|
||||||
|
'This backup file was created using an unsupported version of the application and cannot be imported here. Please update your application and try again.';
|
||||||
|
|
||||||
/** @password_change */
|
/** @password_change */
|
||||||
export const STRING_FAILED_PASSWORD_CHANGE = "There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.";
|
export const STRING_FAILED_PASSWORD_CHANGE =
|
||||||
|
'There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.';
|
||||||
|
|
||||||
export const STRING_CONFIRM_APP_QUIT_DURING_UPGRADE =
|
export const STRING_CONFIRM_APP_QUIT_DURING_UPGRADE =
|
||||||
"The encryption upgrade is in progress. You may lose data if you quit the app. " +
|
'The encryption upgrade is in progress. You may lose data if you quit the app. ' +
|
||||||
"Are you sure you want to quit?"
|
'Are you sure you want to quit?';
|
||||||
|
|
||||||
export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE =
|
export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE =
|
||||||
"A passcode change is in progress. You may lose data if you quit the app. " +
|
'A passcode change is in progress. You may lose data if you quit the app. ' +
|
||||||
"Are you sure you want to quit?"
|
'Are you sure you want to quit?';
|
||||||
|
|
||||||
export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL =
|
export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL =
|
||||||
"A passcode removal is in progress. You may lose data if you quit the app. " +
|
'A passcode removal is in progress. You may lose data if you quit the app. ' +
|
||||||
"Are you sure you want to quit?"
|
'Are you sure you want to quit?';
|
||||||
|
|
||||||
export const STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE = 'Encryption upgrade available';
|
export const STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE =
|
||||||
|
'Encryption upgrade available';
|
||||||
export const STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT =
|
export const STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT =
|
||||||
'Encryption version 004 is available. ' +
|
'Encryption version 004 is available. ' +
|
||||||
'This version strengthens the encryption algorithms your account and ' +
|
'This version strengthens the encryption algorithms your account and ' +
|
||||||
'local storage use. To learn more about this upgrade, visit our ' +
|
'local storage use. To learn more about this upgrade, visit our ' +
|
||||||
'<a href="https://standardnotes.org/help/security" target="_blank">Security Upgrade page.</a>';
|
'<a href="https://standardnotes.org/help/security" target="_blank">Security Upgrade page.</a>';
|
||||||
export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = 'Upgrade';
|
export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = 'Upgrade';
|
||||||
|
|
||||||
|
export const Strings = {
|
||||||
|
keyStorageInfo(application: SNApplication): string | null {
|
||||||
|
if (!isDesktopApplication()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!application.hasAccount()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const platform = getPlatform();
|
||||||
|
const keychainName =
|
||||||
|
platform === Platform.WindowsDesktop
|
||||||
|
? 'credential manager'
|
||||||
|
: platform === Platform.MacDesktop
|
||||||
|
? 'keychain'
|
||||||
|
: 'password manager';
|
||||||
|
return `Your keys are currently stored in your operating system's ${keychainName}. Adding a passcode prevents even your operating system from reading them.`;
|
||||||
|
},
|
||||||
|
protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.',
|
||||||
|
openAccountMenu: 'Open Account Menu'
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"newLine": "lf",
|
"newLine": "lf",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "preact",
|
"jsxImportSource": "preact",
|
||||||
|
"typeRoots": ["./@types"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"%/*": ["../templates/*"],
|
"%/*": ["../templates/*"],
|
||||||
"@/*": ["./*"],
|
"@/*": ["./*"],
|
||||||
|
|||||||
2
app/assets/javascripts/typings/pug.d.ts
vendored
2
app/assets/javascripts/typings/pug.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
declare module "*.pug" {
|
declare module "*.pug" {
|
||||||
import { compileTemplate } from 'pug'
|
import { compileTemplate } from 'pug';
|
||||||
const content: compileTemplate;
|
const content: compileTemplate;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import { isDesktopApplication, isDev } from '@/utils';
|
import { isDesktopApplication, isDev } from '@/utils';
|
||||||
import pull from 'lodash/pull';
|
import pull from 'lodash/pull';
|
||||||
import {
|
import {
|
||||||
ProtectedAction,
|
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
SNTag,
|
SNTag,
|
||||||
SNNote,
|
SNNote,
|
||||||
SNUserPrefs,
|
|
||||||
ContentType,
|
ContentType,
|
||||||
SNSmartTag,
|
|
||||||
PayloadSource,
|
PayloadSource,
|
||||||
DeinitSource,
|
DeinitSource,
|
||||||
UuidString,
|
UuidString,
|
||||||
SyncOpStatus,
|
SyncOpStatus,
|
||||||
|
PrefKey,
|
||||||
|
SNApplication,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { Editor } from '@/ui_models/editor';
|
import { Editor } from '@/ui_models/editor';
|
||||||
import { action, makeObservable, observable } from 'mobx';
|
import { action, makeObservable, observable, runInAction } from 'mobx';
|
||||||
import { Bridge } from '@/services/bridge';
|
import { Bridge } from '@/services/bridge';
|
||||||
|
import { storage, StorageKey } from '@/services/localStorage';
|
||||||
|
|
||||||
export enum AppStateEvent {
|
export enum AppStateEvent {
|
||||||
TagChanged,
|
TagChanged,
|
||||||
@@ -29,6 +29,11 @@ export enum AppStateEvent {
|
|||||||
WindowDidBlur,
|
WindowDidBlur,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PanelResizedData = {
|
||||||
|
panel: string;
|
||||||
|
collapsed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export enum EventSource {
|
export enum EventSource {
|
||||||
UserInteraction,
|
UserInteraction,
|
||||||
Script,
|
Script,
|
||||||
@@ -36,8 +41,6 @@ export enum EventSource {
|
|||||||
|
|
||||||
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>;
|
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>;
|
||||||
|
|
||||||
const SHOW_BETA_WARNING_KEY = 'show_beta_warning';
|
|
||||||
|
|
||||||
class ActionsMenuState {
|
class ActionsMenuState {
|
||||||
hiddenExtensions: Record<UuidString, boolean> = {};
|
hiddenExtensions: Record<UuidString, boolean> = {};
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ class ActionsMenuState {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
hiddenExtensions: observable,
|
hiddenExtensions: observable,
|
||||||
toggleExtensionVisibility: action,
|
toggleExtensionVisibility: action,
|
||||||
deinit: action,
|
reset: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ class ActionsMenuState {
|
|||||||
this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid];
|
this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid];
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
reset() {
|
||||||
this.hiddenExtensions = {};
|
this.hiddenExtensions = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +75,7 @@ export class SyncState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update(status: SyncOpStatus) {
|
update(status: SyncOpStatus): void {
|
||||||
this.errorMessage = status.error?.message;
|
this.errorMessage = status.error?.message;
|
||||||
this.inProgress = status.syncInProgress;
|
this.inProgress = status.syncInProgress;
|
||||||
const stats = status.getStats();
|
const stats = status.getStats();
|
||||||
@@ -92,6 +95,59 @@ export class SyncState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AccountMenuState {
|
||||||
|
show = false;
|
||||||
|
constructor() {
|
||||||
|
makeObservable(this, {
|
||||||
|
show: observable,
|
||||||
|
setShow: action,
|
||||||
|
toggleShow: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setShow(show: boolean) {
|
||||||
|
this.show = show;
|
||||||
|
}
|
||||||
|
toggleShow() {
|
||||||
|
this.show = !this.show;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoAccountWarningState {
|
||||||
|
show: boolean;
|
||||||
|
constructor(application: SNApplication, appObservers: (() => void)[]) {
|
||||||
|
this.show = application.hasAccount()
|
||||||
|
? false
|
||||||
|
: storage.get(StorageKey.ShowNoAccountWarning) ?? true;
|
||||||
|
|
||||||
|
appObservers.push(
|
||||||
|
application.addEventObserver(async () => {
|
||||||
|
runInAction(() => {
|
||||||
|
this.show = false;
|
||||||
|
});
|
||||||
|
}, ApplicationEvent.SignedIn),
|
||||||
|
application.addEventObserver(async () => {
|
||||||
|
if (application.hasAccount()) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.show = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, ApplicationEvent.Started)
|
||||||
|
);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
show: observable,
|
||||||
|
hide: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hide() {
|
||||||
|
this.show = false;
|
||||||
|
storage.set(StorageKey.ShowNoAccountWarning, false);
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
storage.remove(StorageKey.ShowNoAccountWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AppState {
|
export class AppState {
|
||||||
readonly enableUnfinishedFeatures =
|
readonly enableUnfinishedFeatures =
|
||||||
isDev || location.host.includes('app-dev.standardnotes.org');
|
isDev || location.host.includes('app-dev.standardnotes.org');
|
||||||
@@ -106,12 +162,15 @@ export class AppState {
|
|||||||
rootScopeCleanup2: any;
|
rootScopeCleanup2: any;
|
||||||
onVisibilityChange: any;
|
onVisibilityChange: any;
|
||||||
selectedTag?: SNTag;
|
selectedTag?: SNTag;
|
||||||
multiEditorEnabled = false;
|
showBetaWarning: boolean;
|
||||||
showBetaWarning = false;
|
readonly accountMenu = new AccountMenuState();
|
||||||
readonly actionsMenu = new ActionsMenuState();
|
readonly actionsMenu = new ActionsMenuState();
|
||||||
|
readonly noAccountWarning: NoAccountWarningState;
|
||||||
readonly sync = new SyncState();
|
readonly sync = new SyncState();
|
||||||
isSessionsModalVisible = false;
|
isSessionsModalVisible = false;
|
||||||
|
|
||||||
|
private appEventObserverRemovers: (() => void)[] = [];
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
$rootScope: ng.IRootScopeService,
|
$rootScope: ng.IRootScopeService,
|
||||||
@@ -122,15 +181,10 @@ export class AppState {
|
|||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
this.application = application;
|
this.application = application;
|
||||||
makeObservable(this, {
|
this.noAccountWarning = new NoAccountWarningState(
|
||||||
showBetaWarning: observable,
|
application,
|
||||||
isSessionsModalVisible: observable,
|
this.appEventObserverRemovers
|
||||||
|
);
|
||||||
enableBetaWarning: action,
|
|
||||||
disableBetaWarning: action,
|
|
||||||
openSessionsModal: action,
|
|
||||||
closeSessionsModal: action,
|
|
||||||
});
|
|
||||||
this.addAppEventObserver();
|
this.addAppEventObserver();
|
||||||
this.streamNotesAndTags();
|
this.streamNotesAndTags();
|
||||||
this.onVisibilityChange = () => {
|
this.onVisibilityChange = () => {
|
||||||
@@ -141,17 +195,35 @@ export class AppState {
|
|||||||
this.notifyEvent(event);
|
this.notifyEvent(event);
|
||||||
};
|
};
|
||||||
this.registerVisibilityObservers();
|
this.registerVisibilityObservers();
|
||||||
this.determineBetaWarningValue();
|
|
||||||
|
if (this.bridge.appVersion.includes('-beta')) {
|
||||||
|
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true;
|
||||||
|
} else {
|
||||||
|
this.showBetaWarning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit(source: DeinitSource) {
|
makeObservable(this, {
|
||||||
if (source === DeinitSource.SignOut) {
|
showBetaWarning: observable,
|
||||||
localStorage.removeItem(SHOW_BETA_WARNING_KEY);
|
isSessionsModalVisible: observable,
|
||||||
|
|
||||||
|
enableBetaWarning: action,
|
||||||
|
disableBetaWarning: action,
|
||||||
|
openSessionsModal: action,
|
||||||
|
closeSessionsModal: action,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.actionsMenu.deinit();
|
|
||||||
|
deinit(source: DeinitSource): void {
|
||||||
|
if (source === DeinitSource.SignOut) {
|
||||||
|
storage.remove(StorageKey.ShowBetaWarning);
|
||||||
|
this.noAccountWarning.reset();
|
||||||
|
}
|
||||||
|
this.actionsMenu.reset();
|
||||||
this.unsubApp();
|
this.unsubApp();
|
||||||
this.unsubApp = undefined;
|
this.unsubApp = undefined;
|
||||||
this.observers.length = 0;
|
this.observers.length = 0;
|
||||||
|
this.appEventObserverRemovers.forEach((remover) => remover());
|
||||||
|
this.appEventObserverRemovers.length = 0;
|
||||||
if (this.rootScopeCleanup1) {
|
if (this.rootScopeCleanup1) {
|
||||||
this.rootScopeCleanup1();
|
this.rootScopeCleanup1();
|
||||||
this.rootScopeCleanup2();
|
this.rootScopeCleanup2();
|
||||||
@@ -172,30 +244,12 @@ export class AppState {
|
|||||||
|
|
||||||
disableBetaWarning() {
|
disableBetaWarning() {
|
||||||
this.showBetaWarning = false;
|
this.showBetaWarning = false;
|
||||||
localStorage.setItem(SHOW_BETA_WARNING_KEY, 'false');
|
storage.set(StorageKey.ShowBetaWarning, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableBetaWarning() {
|
enableBetaWarning() {
|
||||||
this.showBetaWarning = true;
|
this.showBetaWarning = true;
|
||||||
localStorage.setItem(SHOW_BETA_WARNING_KEY, 'true');
|
storage.set(StorageKey.ShowBetaWarning, true);
|
||||||
}
|
|
||||||
|
|
||||||
clearBetaWarning() {
|
|
||||||
localStorage.setItem(SHOW_BETA_WARNING_KEY, 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
private determineBetaWarningValue() {
|
|
||||||
if (this.bridge.appVersion.includes('-beta')) {
|
|
||||||
switch (localStorage.getItem(SHOW_BETA_WARNING_KEY)) {
|
|
||||||
case 'true':
|
|
||||||
default:
|
|
||||||
this.enableBetaWarning();
|
|
||||||
break;
|
|
||||||
case 'false':
|
|
||||||
this.disableBetaWarning();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,7 +264,7 @@ export class AppState {
|
|||||||
: this.selectedTag.uuid
|
: this.selectedTag.uuid
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (!activeEditor || this.multiEditorEnabled) {
|
if (!activeEditor) {
|
||||||
this.application.editorGroup.createEditor(
|
this.application.editorGroup.createEditor(
|
||||||
undefined,
|
undefined,
|
||||||
title,
|
title,
|
||||||
@@ -221,35 +275,25 @@ export class AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openEditor(noteUuid: string) {
|
async openEditor(noteUuid: string): Promise<void> {
|
||||||
|
if (this.getActiveEditor()?.note?.uuid === noteUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const note = this.application.findItem(noteUuid) as SNNote;
|
const note = this.application.findItem(noteUuid) as SNNote;
|
||||||
if (this.getActiveEditor()?.note?.uuid === noteUuid) return;
|
if (!note) {
|
||||||
const run = async () => {
|
console.warn('Tried accessing a non-existant note of UUID ' + noteUuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.application.authorizeNoteAccess(note)) {
|
||||||
const activeEditor = this.getActiveEditor();
|
const activeEditor = this.getActiveEditor();
|
||||||
if (!activeEditor || this.multiEditorEnabled) {
|
if (!activeEditor) {
|
||||||
this.application.editorGroup.createEditor(noteUuid);
|
this.application.editorGroup.createEditor(noteUuid);
|
||||||
} else {
|
} else {
|
||||||
activeEditor.setNote(note);
|
activeEditor.setNote(note);
|
||||||
}
|
}
|
||||||
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
|
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
|
||||||
};
|
|
||||||
if (
|
|
||||||
note &&
|
|
||||||
note.safeContent.protected &&
|
|
||||||
(await this.application.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ViewProtectedNotes
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.application.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ViewProtectedNotes,
|
|
||||||
() => {
|
|
||||||
run().then(resolve);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +343,11 @@ export class AppState {
|
|||||||
this.closeEditor(editor);
|
this.closeEditor(editor);
|
||||||
} else if (note.trashed && !this.selectedTag?.isTrashTag) {
|
} else if (note.trashed && !this.selectedTag?.isTrashTag) {
|
||||||
this.closeEditor(editor);
|
this.closeEditor(editor);
|
||||||
} else if (note.archived && !this.selectedTag?.isArchiveTag) {
|
} else if (
|
||||||
|
note.archived &&
|
||||||
|
!this.selectedTag?.isArchiveTag &&
|
||||||
|
!this.application.getPreference(PrefKey.NotesShowArchived, false)
|
||||||
|
) {
|
||||||
this.closeEditor(editor);
|
this.closeEditor(editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,10 +448,11 @@ export class AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
panelDidResize(name: string, collapsed: boolean) {
|
panelDidResize(name: string, collapsed: boolean) {
|
||||||
this.notifyEvent(AppStateEvent.PanelResized, {
|
const data: PanelResizedData = {
|
||||||
panel: name,
|
panel: name,
|
||||||
collapsed: collapsed,
|
collapsed: collapsed,
|
||||||
});
|
};
|
||||||
|
this.notifyEvent(AppStateEvent.PanelResized, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
editorDidFocus(eventSource: EventSource) {
|
editorDidFocus(eventSource: EventSource) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PermissionDialog } from '@standardnotes/snjs';
|
|
||||||
import { ComponentModalScope } from './../directives/views/componentModal';
|
import { ComponentModalScope } from './../directives/views/componentModal';
|
||||||
import { AccountSwitcherScope, PermissionsModalScope } from './../types';
|
import { AccountSwitcherScope, PermissionsModalScope } from './../types';
|
||||||
import { ComponentGroup } from './component_group';
|
import { ComponentGroup } from './component_group';
|
||||||
@@ -8,11 +7,12 @@ import { PasswordWizardType, PasswordWizardScope } from '@/types';
|
|||||||
import {
|
import {
|
||||||
SNApplication,
|
SNApplication,
|
||||||
platformFromString,
|
platformFromString,
|
||||||
Challenge,
|
SNComponent,
|
||||||
ProtectedAction, SNComponent
|
PermissionDialog,
|
||||||
|
DeinitSource,
|
||||||
} from '@standardnotes/snjs';
|
} from '@standardnotes/snjs';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import { getPlatformString } from '@/utils';
|
import { getPlatform, getPlatformString } from '@/utils';
|
||||||
import { AlertService } from '@/services/alertService';
|
import { AlertService } from '@/services/alertService';
|
||||||
import { WebDeviceInterface } from '@/web_device_interface';
|
import { WebDeviceInterface } from '@/web_device_interface';
|
||||||
import {
|
import {
|
||||||
@@ -25,26 +25,25 @@ import {
|
|||||||
KeyboardManager
|
KeyboardManager
|
||||||
} from '@/services';
|
} from '@/services';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { SNWebCrypto } from '@standardnotes/sncrypto-web';
|
|
||||||
import { Bridge } from '@/services/bridge';
|
import { Bridge } from '@/services/bridge';
|
||||||
import { DeinitSource } from '@standardnotes/snjs';
|
import { WebCrypto } from '@/crypto';
|
||||||
|
|
||||||
type WebServices = {
|
type WebServices = {
|
||||||
appState: AppState
|
appState: AppState;
|
||||||
desktopService: DesktopManager
|
desktopService: DesktopManager;
|
||||||
autolockService: AutolockService
|
autolockService: AutolockService;
|
||||||
archiveService: ArchiveManager
|
archiveService: ArchiveManager;
|
||||||
nativeExtService: NativeExtManager
|
nativeExtService: NativeExtManager;
|
||||||
statusManager: StatusManager
|
statusManager: StatusManager;
|
||||||
themeService: ThemeManager
|
themeService: ThemeManager;
|
||||||
keyboardService: KeyboardManager
|
keyboardService: KeyboardManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebApplication extends SNApplication {
|
export class WebApplication extends SNApplication {
|
||||||
|
|
||||||
private scope?: ng.IScope
|
private scope?: angular.IScope
|
||||||
private webServices!: WebServices
|
private webServices!: WebServices
|
||||||
private currentAuthenticationElement?: JQLite
|
private currentAuthenticationElement?: angular.IRootElementService
|
||||||
public editorGroup: EditorGroup
|
public editorGroup: EditorGroup
|
||||||
public componentGroup: ComponentGroup
|
public componentGroup: ComponentGroup
|
||||||
|
|
||||||
@@ -52,16 +51,16 @@ export class WebApplication extends SNApplication {
|
|||||||
constructor(
|
constructor(
|
||||||
deviceInterface: WebDeviceInterface,
|
deviceInterface: WebDeviceInterface,
|
||||||
identifier: string,
|
identifier: string,
|
||||||
private $compile: ng.ICompileService,
|
private $compile: angular.ICompileService,
|
||||||
scope: ng.IScope,
|
scope: angular.IScope,
|
||||||
defaultSyncServerHost: string,
|
defaultSyncServerHost: string,
|
||||||
private bridge: Bridge,
|
private bridge: Bridge,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
bridge.environment,
|
bridge.environment,
|
||||||
platformFromString(getPlatformString()),
|
getPlatform(),
|
||||||
deviceInterface,
|
deviceInterface,
|
||||||
new SNWebCrypto(),
|
WebCrypto,
|
||||||
new AlertService(),
|
new AlertService(),
|
||||||
identifier,
|
identifier,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -78,7 +77,7 @@ export class WebApplication extends SNApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
deinit(source: DeinitSource) {
|
deinit(source: DeinitSource): void {
|
||||||
for (const service of Object.values(this.webServices)) {
|
for (const service of Object.values(this.webServices)) {
|
||||||
if ('deinit' in service) {
|
if ('deinit' in service) {
|
||||||
service.deinit?.(source);
|
service.deinit?.(source);
|
||||||
@@ -98,24 +97,24 @@ export class WebApplication extends SNApplication {
|
|||||||
* to complete before destroying the global application instance and all its services */
|
* to complete before destroying the global application instance and all its services */
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
super.deinit(source);
|
super.deinit(source);
|
||||||
}, 0)
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart() {
|
onStart(): void {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
this.componentManager!.openModalComponent = this.openModalComponent;
|
this.componentManager!.openModalComponent = this.openModalComponent;
|
||||||
this.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog;
|
this.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWebServices(services: WebServices) {
|
setWebServices(services: WebServices): void {
|
||||||
this.webServices = services;
|
this.webServices = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAppState() {
|
public getAppState(): AppState {
|
||||||
return this.webServices.appState;
|
return this.webServices.appState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDesktopService() {
|
public getDesktopService(): DesktopManager {
|
||||||
return this.webServices.desktopService;
|
return this.webServices.desktopService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,58 +157,6 @@ export class WebApplication extends SNApplication {
|
|||||||
this.applicationElement.append(el);
|
this.applicationElement.append(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
promptForChallenge(challenge: Challenge) {
|
|
||||||
const scope: any = this.scope!.$new(true);
|
|
||||||
scope.challenge = challenge;
|
|
||||||
scope.application = this;
|
|
||||||
const el = this.$compile!(
|
|
||||||
"<challenge-modal " +
|
|
||||||
"class='sk-modal' application='application' challenge='challenge'>" +
|
|
||||||
"</challenge-modal>"
|
|
||||||
)(scope);
|
|
||||||
this.applicationElement.append(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentPrivilegesModal(
|
|
||||||
action: ProtectedAction,
|
|
||||||
onSuccess?: any,
|
|
||||||
onCancel?: any
|
|
||||||
) {
|
|
||||||
if (this.authenticationInProgress()) {
|
|
||||||
onCancel && onCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customSuccess = async () => {
|
|
||||||
onSuccess && await onSuccess();
|
|
||||||
this.currentAuthenticationElement = undefined;
|
|
||||||
};
|
|
||||||
const customCancel = async () => {
|
|
||||||
onCancel && await onCancel();
|
|
||||||
this.currentAuthenticationElement = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const scope: any = this.scope!.$new(true);
|
|
||||||
scope.action = action;
|
|
||||||
scope.onSuccess = customSuccess;
|
|
||||||
scope.onCancel = customCancel;
|
|
||||||
scope.application = this;
|
|
||||||
const el = this.$compile!(`
|
|
||||||
<privileges-auth-modal application='application' action='action' on-success='onSuccess'
|
|
||||||
on-cancel='onCancel' class='sk-modal'></privileges-auth-modal>
|
|
||||||
`)(scope);
|
|
||||||
this.applicationElement.append(el);
|
|
||||||
|
|
||||||
this.currentAuthenticationElement = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
presentPrivilegesManagementModal() {
|
|
||||||
const scope: any = this.scope!.$new(true);
|
|
||||||
scope.application = this;
|
|
||||||
const el = this.$compile!("<privileges-management-modal application='application' class='sk-modal'></privileges-management-modal>")(scope);
|
|
||||||
this.applicationElement.append(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticationInProgress() {
|
authenticationInProgress() {
|
||||||
return this.currentAuthenticationElement != null;
|
return this.currentAuthenticationElement != null;
|
||||||
}
|
}
|
||||||
@@ -254,7 +201,12 @@ export class WebApplication extends SNApplication {
|
|||||||
this.applicationElement.append(el);
|
this.applicationElement.append(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
openModalComponent(component: SNComponent) {
|
async openModalComponent(component: SNComponent): Promise<void> {
|
||||||
|
if (component.package_info?.identifier === "org.standardnotes.batch-manager") {
|
||||||
|
if (!await this.authorizeBatchManagerAccess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const scope = this.scope!.$new(true) as Partial<ComponentModalScope>;
|
const scope = this.scope!.$new(true) as Partial<ComponentModalScope>;
|
||||||
scope.componentUuid = component.uuid;
|
scope.componentUuid = component.uuid;
|
||||||
scope.application = this;
|
scope.application = this;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { SNComponent, ComponentArea, removeFromArray, addIfUnique } from '@standardnotes/snjs';
|
import { SNComponent, ComponentArea, removeFromArray, addIfUnique , UuidString } from '@standardnotes/snjs';
|
||||||
import { WebApplication } from './application';
|
import { WebApplication } from './application';
|
||||||
import { UuidString } from '@standardnotes/snjs';
|
|
||||||
|
|
||||||
/** Areas that only allow a single component to be active */
|
/** Areas that only allow a single component to be active */
|
||||||
const SingleComponentAreas = [
|
const SingleComponentAreas = [
|
||||||
ComponentArea.Editor,
|
ComponentArea.Editor,
|
||||||
ComponentArea.NoteTags,
|
ComponentArea.NoteTags,
|
||||||
ComponentArea.TagsList
|
ComponentArea.TagsList
|
||||||
]
|
];
|
||||||
|
|
||||||
export class ComponentGroup {
|
export class ComponentGroup {
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export class ComponentGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get componentManager() {
|
get componentManager() {
|
||||||
return this.application?.componentManager!;
|
return this.application.componentManager!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public deinit() {
|
public deinit() {
|
||||||
@@ -91,7 +91,7 @@ export class ComponentGroup {
|
|||||||
callback();
|
callback();
|
||||||
return () => {
|
return () => {
|
||||||
removeFromArray(this.changeObservers, callback);
|
removeFromArray(this.changeObservers, callback);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private notifyObservers() {
|
private notifyObservers() {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class EditorGroup {
|
|||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
removeFromArray(this.changeObservers, callback);
|
removeFromArray(this.changeObservers, callback);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private notifyObservers() {
|
private notifyObservers() {
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
import { Platform, platformFromString } from '@standardnotes/snjs';
|
||||||
|
|
||||||
|
declare const process: {
|
||||||
|
env: {
|
||||||
|
NODE_ENV: string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
export function getPlatformString() {
|
export function getPlatformString() {
|
||||||
@@ -20,15 +28,18 @@ export function getPlatformString() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPlatform(): Platform {
|
||||||
|
return platformFromString(getPlatformString());
|
||||||
|
}
|
||||||
|
|
||||||
let sharedDateFormatter: Intl.DateTimeFormat;
|
let sharedDateFormatter: Intl.DateTimeFormat;
|
||||||
export function dateToLocalizedString(date: Date) {
|
export function dateToLocalizedString(date: Date) {
|
||||||
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
||||||
if (!sharedDateFormatter) {
|
if (!sharedDateFormatter) {
|
||||||
const locale = (
|
const locale =
|
||||||
(navigator.languages && navigator.languages.length)
|
navigator.languages && navigator.languages.length
|
||||||
? navigator.languages[0]
|
? navigator.languages[0]
|
||||||
: navigator.language
|
: navigator.language;
|
||||||
);
|
|
||||||
sharedDateFormatter = new Intl.DateTimeFormat(locale, {
|
sharedDateFormatter = new Intl.DateTimeFormat(locale, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'numeric',
|
month: 'numeric',
|
||||||
@@ -46,11 +57,26 @@ export function dateToLocalizedString(date: Date) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSameDay(dateA: Date, dateB: Date): boolean {
|
||||||
|
return (
|
||||||
|
dateA.getFullYear() === dateB.getFullYear() &&
|
||||||
|
dateA.getMonth() === dateB.getMonth() &&
|
||||||
|
dateA.getDate() === dateB.getDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Via https://davidwalsh.name/javascript-debounce-function */
|
/** Via https://davidwalsh.name/javascript-debounce-function */
|
||||||
export function debounce(this: any, func: any, wait: number, immediate = false) {
|
export function debounce(
|
||||||
|
this: any,
|
||||||
|
func: any,
|
||||||
|
wait: number,
|
||||||
|
immediate = false
|
||||||
|
) {
|
||||||
let timeout: any;
|
let timeout: any;
|
||||||
return () => {
|
return () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const context = this;
|
const context = this;
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
const args = arguments;
|
const args = arguments;
|
||||||
const later = function () {
|
const later = function () {
|
||||||
timeout = null;
|
timeout = null;
|
||||||
@@ -61,7 +87,7 @@ export function debounce(this: any, func: any, wait: number, immediate = false)
|
|||||||
timeout = setTimeout(later, wait);
|
timeout = setTimeout(later, wait);
|
||||||
if (callNow) func.apply(context, args);
|
if (callNow) func.apply(context, args);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
||||||
if (!Array.prototype.includes) {
|
if (!Array.prototype.includes) {
|
||||||
@@ -73,10 +99,10 @@ if (!Array.prototype.includes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Let O be ? ToObject(this value).
|
// 1. Let O be ? ToObject(this value).
|
||||||
var o = Object(this);
|
const o = Object(this);
|
||||||
|
|
||||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||||
var len = o.length >>> 0;
|
const len = o.length >>> 0;
|
||||||
|
|
||||||
// 3. If len is 0, return false.
|
// 3. If len is 0, return false.
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
@@ -85,14 +111,14 @@ if (!Array.prototype.includes) {
|
|||||||
|
|
||||||
// 4. Let n be ? ToInteger(fromIndex).
|
// 4. Let n be ? ToInteger(fromIndex).
|
||||||
// (If fromIndex is undefined, this step produces the value 0.)
|
// (If fromIndex is undefined, this step produces the value 0.)
|
||||||
var n = fromIndex | 0;
|
const n = fromIndex | 0;
|
||||||
|
|
||||||
// 5. If n ≥ 0, then
|
// 5. If n ≥ 0, then
|
||||||
// a. Let k be n.
|
// a. Let k be n.
|
||||||
// 6. Else n < 0,
|
// 6. Else n < 0,
|
||||||
// a. Let k be len + n.
|
// a. Let k be len + n.
|
||||||
// b. If k < 0, let k be 0.
|
// b. If k < 0, let k be 0.
|
||||||
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||||
|
|
||||||
function sameValueZero(x: number, y: number) {
|
function sameValueZero(x: number, y: number) {
|
||||||
return (
|
return (
|
||||||
@@ -117,7 +143,7 @@ if (!Array.prototype.includes) {
|
|||||||
|
|
||||||
// 8. Return false
|
// 8. Return false
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +165,9 @@ declare const __WEB__: boolean;
|
|||||||
declare const __DESKTOP__: boolean;
|
declare const __DESKTOP__: boolean;
|
||||||
|
|
||||||
if (!__WEB__ && !__DESKTOP__) {
|
if (!__WEB__ && !__DESKTOP__) {
|
||||||
throw Error('Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.');
|
throw Error(
|
||||||
|
'Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDesktopApplication() {
|
export function isDesktopApplication() {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
...this.getInitialState(),
|
...this.getInitialState(),
|
||||||
...this.state,
|
...this.state,
|
||||||
}
|
};
|
||||||
this.addAppEventObserver();
|
this.addAppEventObserver();
|
||||||
this.addAppStateObserver();
|
this.addAppStateObserver();
|
||||||
this.templateReady = true;
|
this.templateReady = true;
|
||||||
@@ -77,10 +77,16 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
|||||||
*/
|
*/
|
||||||
this.state = Object.freeze(Object.assign({}, this.state, state));
|
this.state = Object.freeze(Object.assign({}, this.state, state));
|
||||||
resolve();
|
resolve();
|
||||||
|
this.afterStateChange();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
afterStateChange(): void {
|
||||||
|
}
|
||||||
|
|
||||||
/** @returns a promise that resolves after the UI has been updated. */
|
/** @returns a promise that resolves after the UI has been updated. */
|
||||||
flushUI() {
|
flushUI() {
|
||||||
return this.$timeout();
|
return this.$timeout();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||||
import { WebDirective } from '@/types';
|
import { WebDirective } from '@/types';
|
||||||
|
|
||||||
class AccountSwitcherCtrl extends PureViewCtrl<{}, {
|
class AccountSwitcherCtrl extends PureViewCtrl<unknown, {
|
||||||
descriptors: ApplicationDescriptor[];
|
descriptors: ApplicationDescriptor[];
|
||||||
editingDescriptor?: ApplicationDescriptor
|
editingDescriptor?: ApplicationDescriptor
|
||||||
}> {
|
}> {
|
||||||
@@ -38,7 +38,7 @@ class AccountSwitcherCtrl extends PureViewCtrl<{}, {
|
|||||||
reloadApplications() {
|
reloadApplications() {
|
||||||
this.setState({
|
this.setState({
|
||||||
descriptors: this.mainApplicationGroup.getDescriptors()
|
descriptors: this.mainApplicationGroup.getDescriptors()
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @template */
|
/** @template */
|
||||||
@@ -63,7 +63,7 @@ class AccountSwitcherCtrl extends PureViewCtrl<{}, {
|
|||||||
this.setState({ editingDescriptor: descriptor }).then(() => {
|
this.setState({ editingDescriptor: descriptor }).then(() => {
|
||||||
const input = this.inputForDescriptor(descriptor);
|
const input = this.inputForDescriptor(descriptor);
|
||||||
input?.focus();
|
input?.focus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @template */
|
/** @template */
|
||||||
@@ -71,7 +71,7 @@ class AccountSwitcherCtrl extends PureViewCtrl<{}, {
|
|||||||
this.mainApplicationGroup.renameDescriptor(
|
this.mainApplicationGroup.renameDescriptor(
|
||||||
this.state.editingDescriptor!,
|
this.state.editingDescriptor!,
|
||||||
this.state.editingDescriptor!.label
|
this.state.editingDescriptor!.label
|
||||||
)
|
);
|
||||||
this.setState({ editingDescriptor: undefined });
|
this.setState({ editingDescriptor: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,4 +26,12 @@
|
|||||||
path(d="M480 256l-75.53-33.53L256.1 290.6l-148.77-68.17L32 256l224 102 224-102z")
|
path(d="M480 256l-75.53-33.53L256.1 290.6l-148.77-68.17L32 256l224 102 224-102z")
|
||||||
sessions-modal(
|
sessions-modal(
|
||||||
application='self.application'
|
application='self.application'
|
||||||
|
app-state='self.appState'
|
||||||
|
)
|
||||||
|
challenge-modal(
|
||||||
|
ng-repeat="challenge in self.challenges track by challenge.id"
|
||||||
|
class="sk-modal"
|
||||||
|
application="self.application"
|
||||||
|
challenge="challenge"
|
||||||
|
on-dismiss="self.removeChallenge(challenge)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { RootScopeMessages } from './../../messages';
|
|||||||
import { WebDirective } from '@/types';
|
import { WebDirective } from '@/types';
|
||||||
import { getPlatformString } from '@/utils';
|
import { getPlatformString } from '@/utils';
|
||||||
import template from './application-view.pug';
|
import template from './application-view.pug';
|
||||||
import { AppStateEvent } from '@/ui_models/app_state';
|
import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state';
|
||||||
import { ApplicationEvent } from '@standardnotes/snjs';
|
import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs';
|
||||||
import {
|
import {
|
||||||
PANEL_NAME_NOTES,
|
PANEL_NAME_NOTES,
|
||||||
PANEL_NAME_TAGS
|
PANEL_NAME_TAGS
|
||||||
@@ -14,37 +14,44 @@ import {
|
|||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||||
import { alertDialog } from '@/services/alertService';
|
import { alertDialog } from '@/services/alertService';
|
||||||
|
|
||||||
class ApplicationViewCtrl extends PureViewCtrl {
|
class ApplicationViewCtrl extends PureViewCtrl<unknown, {
|
||||||
private $location?: ng.ILocationService
|
ready?: boolean,
|
||||||
private $rootScope?: ng.IRootScopeService
|
needsUnlock?: boolean,
|
||||||
|
appClass: string,
|
||||||
|
}> {
|
||||||
public platformString: string
|
public platformString: string
|
||||||
private notesCollapsed = false
|
private notesCollapsed = false
|
||||||
private tagsCollapsed = false
|
private tagsCollapsed = false
|
||||||
|
/**
|
||||||
|
* To prevent stale state reads (setState is async),
|
||||||
|
* challenges is a mutable array
|
||||||
|
*/
|
||||||
|
private challenges: Challenge[] = [];
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
$location: ng.ILocationService,
|
private $location: ng.ILocationService,
|
||||||
$rootScope: ng.IRootScopeService,
|
private $rootScope: ng.IRootScopeService,
|
||||||
$timeout: ng.ITimeoutService
|
$timeout: ng.ITimeoutService
|
||||||
) {
|
) {
|
||||||
super($timeout);
|
super($timeout);
|
||||||
this.$location = $location;
|
this.$location = $location;
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
this.platformString = getPlatformString();
|
this.platformString = getPlatformString();
|
||||||
this.state = { appClass: '' };
|
this.state = this.getInitialState();
|
||||||
this.onDragDrop = this.onDragDrop.bind(this);
|
this.onDragDrop = this.onDragDrop.bind(this);
|
||||||
this.onDragOver = this.onDragOver.bind(this);
|
this.onDragOver = this.onDragOver.bind(this);
|
||||||
this.addDragDropHandlers();
|
this.addDragDropHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
this.$location = undefined;
|
(this.$location as unknown) = undefined;
|
||||||
this.$rootScope = undefined;
|
(this.$rootScope as unknown) = undefined;
|
||||||
(this.application as any) = undefined;
|
(this.application as unknown) = undefined;
|
||||||
window.removeEventListener('dragover', this.onDragOver, true);
|
window.removeEventListener('dragover', this.onDragOver, true);
|
||||||
window.removeEventListener('drop', this.onDragDrop, true);
|
window.removeEventListener('drop', this.onDragDrop, true);
|
||||||
(this.onDragDrop as any) = undefined;
|
(this.onDragDrop as unknown) = undefined;
|
||||||
(this.onDragOver as any) = undefined;
|
(this.onDragOver as unknown) = undefined;
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,23 +60,38 @@ class ApplicationViewCtrl extends PureViewCtrl {
|
|||||||
this.loadApplication();
|
this.loadApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
appClass: '',
|
||||||
|
challenges: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async loadApplication() {
|
async loadApplication() {
|
||||||
this.application!.componentManager!.setDesktopManager(
|
this.application.componentManager.setDesktopManager(
|
||||||
this.application!.getDesktopService()
|
this.application.getDesktopService()
|
||||||
);
|
);
|
||||||
await this.application!.prepareForLaunch({
|
await this.application.prepareForLaunch({
|
||||||
receiveChallenge: async (challenge) => {
|
receiveChallenge: async (challenge) => {
|
||||||
this.application!.promptForChallenge(challenge);
|
this.$timeout(() => {
|
||||||
|
this.challenges.push(challenge);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.application!.launch();
|
await this.application.launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeChallenge(challenge: Challenge) {
|
||||||
|
this.$timeout(() => {
|
||||||
|
removeFromArray(this.challenges, challenge);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAppStart() {
|
async onAppStart() {
|
||||||
super.onAppStart();
|
super.onAppStart();
|
||||||
this.setState({
|
this.setState({
|
||||||
ready: true,
|
ready: true,
|
||||||
needsUnlock: this.application!.hasPasscode()
|
needsUnlock: this.application.hasPasscode()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +102,8 @@ class ApplicationViewCtrl extends PureViewCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpdateAvailable() {
|
onUpdateAvailable() {
|
||||||
this.$rootScope!.$broadcast(RootScopeMessages.NewUpdateAvailable);
|
this.$rootScope.$broadcast(RootScopeMessages.NewUpdateAvailable);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async onAppEvent(eventName: ApplicationEvent) {
|
async onAppEvent(eventName: ApplicationEvent) {
|
||||||
@@ -98,21 +120,22 @@ class ApplicationViewCtrl extends PureViewCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async onAppStateEvent(eventName: AppStateEvent, data?: any) {
|
async onAppStateEvent(eventName: AppStateEvent, data?: unknown) {
|
||||||
if (eventName === AppStateEvent.PanelResized) {
|
if (eventName === AppStateEvent.PanelResized) {
|
||||||
if (data.panel === PANEL_NAME_NOTES) {
|
const { panel, collapsed } = data as PanelResizedData;
|
||||||
this.notesCollapsed = data.collapsed;
|
if (panel === PANEL_NAME_NOTES) {
|
||||||
|
this.notesCollapsed = collapsed;
|
||||||
}
|
}
|
||||||
if (data.panel === PANEL_NAME_TAGS) {
|
if (panel === PANEL_NAME_TAGS) {
|
||||||
this.tagsCollapsed = data.collapsed;
|
this.tagsCollapsed = collapsed;
|
||||||
}
|
}
|
||||||
let appClass = "";
|
let appClass = "";
|
||||||
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
|
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
|
||||||
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
|
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
|
||||||
this.setState({ appClass });
|
this.setState({ appClass });
|
||||||
} else if (eventName === AppStateEvent.WindowDidFocus) {
|
} else if (eventName === AppStateEvent.WindowDidFocus) {
|
||||||
if (!(await this.application!.isLocked())) {
|
if (!(await this.application.isLocked())) {
|
||||||
this.application!.sync();
|
this.application.sync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,29 +151,29 @@ class ApplicationViewCtrl extends PureViewCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDragOver(event: DragEvent) {
|
onDragOver(event: DragEvent) {
|
||||||
if (event.dataTransfer!.files.length > 0) {
|
if (event.dataTransfer?.files.length) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragDrop(event: DragEvent) {
|
onDragDrop(event: DragEvent) {
|
||||||
if (event.dataTransfer!.files.length > 0) {
|
if (event.dataTransfer?.files.length) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.application!.alertService!.alert(
|
void alertDialog({
|
||||||
STRING_DEFAULT_FILE_ERROR
|
text: STRING_DEFAULT_FILE_ERROR
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDemoSignInFromParams() {
|
async handleDemoSignInFromParams() {
|
||||||
if (
|
if (
|
||||||
this.$location!.search().demo === 'true' &&
|
this.$location.search().demo === 'true' &&
|
||||||
!this.application.hasAccount()
|
!this.application.hasAccount()
|
||||||
) {
|
) {
|
||||||
await this.application!.setHost(
|
await this.application.setHost(
|
||||||
'https://syncing-server-demo.standardnotes.org'
|
'https://syncing-server-demo.standardnotes.org'
|
||||||
);
|
);
|
||||||
this.application!.signIn(
|
this.application.signIn(
|
||||||
'demo@standardnotes.org',
|
'demo@standardnotes.org',
|
||||||
'password',
|
'password',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
.sk-modal-background(ng-click="ctrl.cancel()")
|
|
||||||
.challenge-modal.sk-modal-content(ng-if='ctrl.templateReady')
|
|
||||||
.sn-component
|
|
||||||
.sk-panel
|
|
||||||
.sk-panel-header
|
|
||||||
.sk-panel-header-title {{ctrl.challenge.modalTitle}}
|
|
||||||
.sk-panel-content
|
|
||||||
.sk-panel-section
|
|
||||||
.sk-p.sk-panel-row.centered.prompt
|
|
||||||
strong {{ctrl.challenge.heading}}
|
|
||||||
.sk-p.sk-panel-row.centered.subprompt(ng-if='ctrl.challenge.subheading')
|
|
||||||
| {{ctrl.challenge.subheading}}
|
|
||||||
.sk-panel-section
|
|
||||||
div(ng-repeat="prompt in ctrl.state.prompts track by prompt.id")
|
|
||||||
.sk-panel-row
|
|
||||||
input.sk-input.contrast(
|
|
||||||
ng-model="ctrl.state.values[prompt.id].value"
|
|
||||||
should-focus="$index == 0"
|
|
||||||
sn-autofocus="true"
|
|
||||||
sn-enter="ctrl.submit()" ,
|
|
||||||
ng-change="ctrl.onTextValueChange(prompt)"
|
|
||||||
ng-attr-type="{{prompt.secureTextEntry ? 'password' : 'text'}}",
|
|
||||||
ng-attr-placeholder="{{prompt.title}}"
|
|
||||||
)
|
|
||||||
.sk-panel-row.centered
|
|
||||||
label.sk-label.danger(
|
|
||||||
ng-if="ctrl.state.values[prompt.id].invalid"
|
|
||||||
) Invalid authentication. Please try again.
|
|
||||||
.sk-panel-footer.extra-padding
|
|
||||||
.sk-button.info.big.block.bold(
|
|
||||||
ng-click="ctrl.submit()",
|
|
||||||
ng-class="{'info' : !ctrl.state.processing, 'neutral': ctrl.state.processing}"
|
|
||||||
ng-disabled="ctrl.state.processing"
|
|
||||||
)
|
|
||||||
.sk-label {{ctrl.state.processing ? 'Generating Keys...' : 'Submit'}}
|
|
||||||
.sk-panel-row(ng-if="ctrl.challenge.cancelable")
|
|
||||||
a.sk-panel-row.sk-a.info.centered(
|
|
||||||
ng-if="ctrl.challenge.cancelable"
|
|
||||||
ng-click="ctrl.cancel()"
|
|
||||||
) Cancel
|
|
||||||
|
|
||||||
.sk-panel-footer(ng-if="ctrl.state.showForgotPasscodeLink")
|
|
||||||
a.sk-panel-row.sk-a.info.centered(
|
|
||||||
ng-if="!ctrl.state.forgotPasscode"
|
|
||||||
ng-click="ctrl.onForgotPasscodeClick()"
|
|
||||||
) Forgot your passcode?
|
|
||||||
p.sk-panel-row.sk-p(ng-if="ctrl.state.forgotPasscode").
|
|
||||||
{{
|
|
||||||
ctrl.state.hasAccount
|
|
||||||
? "If you forgot your application passcode, your only option is to clear
|
|
||||||
your local data from this device and sign back in to your account."
|
|
||||||
: "If you forgot your application passcode, your only option is
|
|
||||||
to delete your data."
|
|
||||||
}}
|
|
||||||
a.sk-panel-row.sk-a.danger.centered(
|
|
||||||
ng-if="ctrl.state.forgotPasscode"
|
|
||||||
ng-click="ctrl.destroyLocalData()"
|
|
||||||
) Delete Local Data
|
|
||||||
.sk-panel-row
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import template from './challenge-modal.pug';
|
|
||||||
import {
|
|
||||||
ChallengeValue,
|
|
||||||
removeFromArray,
|
|
||||||
Challenge,
|
|
||||||
ChallengeReason,
|
|
||||||
ChallengePrompt
|
|
||||||
} from '@standardnotes/snjs';
|
|
||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
|
||||||
import { WebDirective } from '@/types';
|
|
||||||
import { confirmDialog } from '@/services/alertService';
|
|
||||||
import {
|
|
||||||
STRING_SIGN_OUT_CONFIRMATION,
|
|
||||||
} from '@/strings';
|
|
||||||
|
|
||||||
type InputValue = {
|
|
||||||
prompt: ChallengePrompt;
|
|
||||||
value: string;
|
|
||||||
invalid: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Values = Record<number, InputValue>
|
|
||||||
|
|
||||||
type ChallengeModalState = {
|
|
||||||
prompts: ChallengePrompt[]
|
|
||||||
values: Partial<Values>
|
|
||||||
processing: boolean,
|
|
||||||
forgotPasscode: boolean,
|
|
||||||
showForgotPasscodeLink: boolean,
|
|
||||||
processingPrompts: ChallengePrompt[],
|
|
||||||
hasAccount: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
|
|
||||||
private $element: JQLite
|
|
||||||
application!: WebApplication
|
|
||||||
challenge!: Challenge
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor(
|
|
||||||
$element: JQLite,
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
) {
|
|
||||||
super($timeout);
|
|
||||||
this.$element = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
getState() {
|
|
||||||
return this.state as ChallengeModalState;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
super.$onInit();
|
|
||||||
const values = {} as Values;
|
|
||||||
const prompts = this.challenge.prompts;
|
|
||||||
for (const prompt of prompts) {
|
|
||||||
values[prompt.id] = {
|
|
||||||
prompt,
|
|
||||||
value: '',
|
|
||||||
invalid: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const showForgotPasscodeLink = [
|
|
||||||
ChallengeReason.ApplicationUnlock,
|
|
||||||
ChallengeReason.Migration
|
|
||||||
].includes(this.challenge.reason);
|
|
||||||
this.setState({
|
|
||||||
prompts,
|
|
||||||
values,
|
|
||||||
processing: false,
|
|
||||||
forgotPasscode: false,
|
|
||||||
showForgotPasscodeLink,
|
|
||||||
hasAccount: this.application.hasAccount(),
|
|
||||||
processingPrompts: []
|
|
||||||
});
|
|
||||||
this.application.addChallengeObserver(
|
|
||||||
this.challenge,
|
|
||||||
{
|
|
||||||
onValidValue: (value) => {
|
|
||||||
this.getState().values[value.prompt.id]!.invalid = false;
|
|
||||||
removeFromArray(this.state.processingPrompts, value.prompt);
|
|
||||||
this.reloadProcessingStatus();
|
|
||||||
},
|
|
||||||
onInvalidValue: (value) => {
|
|
||||||
this.getState().values[value.prompt.id]!.invalid = true;
|
|
||||||
/** If custom validation, treat all values together and not individually */
|
|
||||||
if (!value.prompt.validates) {
|
|
||||||
this.setState({ processingPrompts: [], processing: false });
|
|
||||||
} else {
|
|
||||||
removeFromArray(this.state.processingPrompts, value.prompt);
|
|
||||||
this.reloadProcessingStatus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.dismiss();
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
this.dismiss();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit() {
|
|
||||||
(this.application as any) = undefined;
|
|
||||||
(this.challenge as any) = undefined;
|
|
||||||
super.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadProcessingStatus() {
|
|
||||||
return this.setState({
|
|
||||||
processing: this.state.processingPrompts.length > 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroyLocalData() {
|
|
||||||
if (await confirmDialog({
|
|
||||||
text: STRING_SIGN_OUT_CONFIRMATION,
|
|
||||||
confirmButtonStyle: "danger"
|
|
||||||
})) {
|
|
||||||
await this.application.signOut();
|
|
||||||
this.dismiss();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @template */
|
|
||||||
cancel() {
|
|
||||||
if (this.challenge.cancelable) {
|
|
||||||
this.application!.cancelChallenge(this.challenge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onForgotPasscodeClick() {
|
|
||||||
this.setState({
|
|
||||||
forgotPasscode: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextValueChange(prompt: ChallengePrompt) {
|
|
||||||
const values = this.getState().values;
|
|
||||||
values[prompt.id]!.invalid = false;
|
|
||||||
this.setState({ values });
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
const failed = [];
|
|
||||||
for (const prompt of this.getState().prompts) {
|
|
||||||
const value = this.getState().values[prompt.id];
|
|
||||||
if (!value || value.value.length === 0) {
|
|
||||||
this.getState().values[prompt.id]!.invalid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return failed.length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
if (!this.validate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.setState({ processing: true });
|
|
||||||
const values: ChallengeValue[] = [];
|
|
||||||
for (const inputValue of Object.values(this.getState().values)) {
|
|
||||||
const rawValue = inputValue!!.value;
|
|
||||||
const value = new ChallengeValue(inputValue!.prompt, rawValue);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
const processingPrompts = values.map((v) => v.prompt);
|
|
||||||
await this.setState({
|
|
||||||
processingPrompts: processingPrompts,
|
|
||||||
processing: processingPrompts.length > 0
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Unfortunately neccessary to wait 50ms so that the above setState call completely
|
|
||||||
* updates the UI to change processing state, before we enter into UI blocking operation
|
|
||||||
* (crypto key generation)
|
|
||||||
*/
|
|
||||||
this.$timeout(() => {
|
|
||||||
if (values.length > 0) {
|
|
||||||
this.application.submitValuesForChallenge(this.challenge, values);
|
|
||||||
} else {
|
|
||||||
this.setState({ processing: false });
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss() {
|
|
||||||
const elem = this.$element;
|
|
||||||
const scope = elem.scope();
|
|
||||||
scope.$destroy();
|
|
||||||
elem.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChallengeModal extends WebDirective {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.restrict = 'E';
|
|
||||||
this.template = template;
|
|
||||||
this.controller = ChallengeModalCtrl;
|
|
||||||
this.controllerAs = 'ctrl';
|
|
||||||
this.bindToController = true;
|
|
||||||
this.scope = {
|
|
||||||
challenge: '=',
|
|
||||||
application: '='
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
409
app/assets/javascripts/views/challenge_modal/challenge_modal.tsx
Normal file
409
app/assets/javascripts/views/challenge_modal/challenge_modal.tsx
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { Dialog } from '@reach/dialog';
|
||||||
|
import {
|
||||||
|
ChallengeValue,
|
||||||
|
removeFromArray,
|
||||||
|
Challenge,
|
||||||
|
ChallengeReason,
|
||||||
|
ChallengePrompt,
|
||||||
|
ChallengeValidation,
|
||||||
|
ProtectionSessionDurations,
|
||||||
|
} from '@standardnotes/snjs';
|
||||||
|
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||||
|
import { WebDirective } from '@/types';
|
||||||
|
import { confirmDialog } from '@/services/alertService';
|
||||||
|
import { STRING_SIGN_OUT_CONFIRMATION } from '@/strings';
|
||||||
|
import { Ref, render } from 'preact';
|
||||||
|
import { useRef } from 'preact/hooks';
|
||||||
|
import ng from 'angular';
|
||||||
|
|
||||||
|
type InputValue = {
|
||||||
|
prompt: ChallengePrompt;
|
||||||
|
value: string | number | boolean;
|
||||||
|
invalid: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Values = Record<number, InputValue>;
|
||||||
|
|
||||||
|
type ChallengeModalState = {
|
||||||
|
prompts: ChallengePrompt[];
|
||||||
|
values: Partial<Values>;
|
||||||
|
processing: boolean;
|
||||||
|
forgotPasscode: boolean;
|
||||||
|
showForgotPasscodeLink: boolean;
|
||||||
|
processingPrompts: ChallengePrompt[];
|
||||||
|
hasAccount: boolean;
|
||||||
|
protectedNoteAccessDuration: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChallengeModalCtrl extends PureViewCtrl<unknown, ChallengeModalState> {
|
||||||
|
application!: WebApplication;
|
||||||
|
challenge!: Challenge;
|
||||||
|
onDismiss!: () => void;
|
||||||
|
submitting = false;
|
||||||
|
|
||||||
|
/** @template */
|
||||||
|
protectionsSessionDurations = ProtectionSessionDurations;
|
||||||
|
protectionsSessionValidation = ChallengeValidation.ProtectionSessionDuration;
|
||||||
|
|
||||||
|
/* @ngInject */
|
||||||
|
constructor(
|
||||||
|
private $element: ng.IRootElementService,
|
||||||
|
$timeout: ng.ITimeoutService
|
||||||
|
) {
|
||||||
|
super($timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return this.state as ChallengeModalState;
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
super.$onInit();
|
||||||
|
const values = {} as Values;
|
||||||
|
const prompts = this.challenge.prompts;
|
||||||
|
for (const prompt of prompts) {
|
||||||
|
values[prompt.id] = {
|
||||||
|
prompt,
|
||||||
|
value: prompt.initialValue ?? '',
|
||||||
|
invalid: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const showForgotPasscodeLink = [
|
||||||
|
ChallengeReason.ApplicationUnlock,
|
||||||
|
ChallengeReason.Migration,
|
||||||
|
].includes(this.challenge.reason);
|
||||||
|
this.setState({
|
||||||
|
prompts,
|
||||||
|
values,
|
||||||
|
processing: false,
|
||||||
|
forgotPasscode: false,
|
||||||
|
showForgotPasscodeLink,
|
||||||
|
hasAccount: this.application.hasAccount(),
|
||||||
|
processingPrompts: [],
|
||||||
|
protectedNoteAccessDuration: ProtectionSessionDurations[0].valueInSeconds,
|
||||||
|
});
|
||||||
|
this.application.addChallengeObserver(this.challenge, {
|
||||||
|
onValidValue: (value) => {
|
||||||
|
this.state.values[value.prompt.id]!.invalid = false;
|
||||||
|
removeFromArray(this.state.processingPrompts, value.prompt);
|
||||||
|
this.reloadProcessingStatus();
|
||||||
|
/** Trigger UI update */
|
||||||
|
this.afterStateChange();
|
||||||
|
},
|
||||||
|
onInvalidValue: (value) => {
|
||||||
|
this.state.values[value.prompt.id]!.invalid = true;
|
||||||
|
/** If custom validation, treat all values together and not individually */
|
||||||
|
if (!value.prompt.validates) {
|
||||||
|
this.setState({ processingPrompts: [], processing: false });
|
||||||
|
} else {
|
||||||
|
removeFromArray(this.state.processingPrompts, value.prompt);
|
||||||
|
this.reloadProcessingStatus();
|
||||||
|
}
|
||||||
|
/** Trigger UI update */
|
||||||
|
this.afterStateChange();
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.dismiss();
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
this.dismiss();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit() {
|
||||||
|
(this.application as any) = undefined;
|
||||||
|
(this.challenge as any) = undefined;
|
||||||
|
super.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadProcessingStatus() {
|
||||||
|
return this.setState({
|
||||||
|
processing: this.state.processingPrompts.length > 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroyLocalData() {
|
||||||
|
if (
|
||||||
|
await confirmDialog({
|
||||||
|
text: STRING_SIGN_OUT_CONFIRMATION,
|
||||||
|
confirmButtonStyle: 'danger',
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
await this.application.signOut();
|
||||||
|
this.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @template */
|
||||||
|
cancel() {
|
||||||
|
if (this.challenge.cancelable) {
|
||||||
|
this.application!.cancelChallenge(this.challenge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onForgotPasscodeClick() {
|
||||||
|
this.setState({
|
||||||
|
forgotPasscode: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextValueChange(prompt: ChallengePrompt) {
|
||||||
|
const values = this.getState().values;
|
||||||
|
values[prompt.id]!.invalid = false;
|
||||||
|
this.setState({ values });
|
||||||
|
}
|
||||||
|
|
||||||
|
onNumberValueChange(prompt: ChallengePrompt, value: number) {
|
||||||
|
const values = this.state.values;
|
||||||
|
values[prompt.id]!.invalid = false;
|
||||||
|
values[prompt.id]!.value = value;
|
||||||
|
this.setState({ values });
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
let failed = 0;
|
||||||
|
for (const prompt of this.state.prompts) {
|
||||||
|
const value = this.state.values[prompt.id]!;
|
||||||
|
if (typeof value.value === 'string' && value.value.length === 0) {
|
||||||
|
this.state.values[prompt.id]!.invalid = true;
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return failed === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (!this.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.submitting = true;
|
||||||
|
await this.setState({ processing: true });
|
||||||
|
const values: ChallengeValue[] = [];
|
||||||
|
for (const inputValue of Object.values(this.getState().values)) {
|
||||||
|
const rawValue = inputValue!.value;
|
||||||
|
const value = new ChallengeValue(inputValue!.prompt, rawValue);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
const processingPrompts = values.map((v) => v.prompt);
|
||||||
|
await this.setState({
|
||||||
|
processingPrompts: processingPrompts,
|
||||||
|
processing: processingPrompts.length > 0,
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Unfortunately neccessary to wait 50ms so that the above setState call completely
|
||||||
|
* updates the UI to change processing state, before we enter into UI blocking operation
|
||||||
|
* (crypto key generation)
|
||||||
|
*/
|
||||||
|
this.$timeout(() => {
|
||||||
|
if (values.length > 0) {
|
||||||
|
this.application.submitValuesForChallenge(this.challenge, values);
|
||||||
|
} else {
|
||||||
|
this.setState({ processing: false });
|
||||||
|
}
|
||||||
|
this.submitting = false;
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterStateChange() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.onDismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
$onDestroy() {
|
||||||
|
render(<></>, this.$element[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private render() {
|
||||||
|
if (!this.state.prompts) return;
|
||||||
|
render(<ChallengeModalView ctrl={this} />, this.$element[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChallengeModal extends WebDirective {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.restrict = 'E';
|
||||||
|
// this.template = template;
|
||||||
|
this.controller = ChallengeModalCtrl;
|
||||||
|
this.controllerAs = 'ctrl';
|
||||||
|
this.bindToController = true;
|
||||||
|
this.scope = {
|
||||||
|
challenge: '=',
|
||||||
|
application: '=',
|
||||||
|
onDismiss: '&',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChallengeModalView({ ctrl }: { ctrl: ChallengeModalCtrl }) {
|
||||||
|
const initialFocusRef = useRef<HTMLInputElement>();
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
initialFocusRef={initialFocusRef}
|
||||||
|
onDismiss={() => {
|
||||||
|
if (ctrl.challenge.cancelable) {
|
||||||
|
ctrl.cancel();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="challenge-modal sk-modal-content">
|
||||||
|
<div className="sn-component">
|
||||||
|
<div className="sk-panel">
|
||||||
|
<div className="sk-panel-header">
|
||||||
|
<div className="sk-panel-header-title">
|
||||||
|
{ctrl.challenge.modalTitle}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sk-panel-content">
|
||||||
|
<div className="sk-panel-section">
|
||||||
|
<div className="sk-p sk-panel-row centered prompt">
|
||||||
|
<strong>{ctrl.challenge.heading}</strong>
|
||||||
|
</div>
|
||||||
|
{ctrl.challenge.subheading && (
|
||||||
|
<div className="sk-p sk-panel-row centered subprompt">
|
||||||
|
{ctrl.challenge.subheading}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sk-panel-section">
|
||||||
|
{ChallengePrompts({ ctrl, initialFocusRef })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="sk-panel-footer extra-padding">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'sk-button big block bold ' +
|
||||||
|
(ctrl.state.processing ? 'neutral' : 'info')
|
||||||
|
}
|
||||||
|
disabled={ctrl.state.processing}
|
||||||
|
onClick={() => ctrl.submit()}
|
||||||
|
>
|
||||||
|
<div className="sk-label">
|
||||||
|
{ctrl.state.processing ? 'Generating Keys…' : 'Submit'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ctrl.challenge.cancelable && (
|
||||||
|
<>
|
||||||
|
<div className="sk-panel-row"></div>
|
||||||
|
<a
|
||||||
|
className="sk-panel-row sk-a info centered"
|
||||||
|
onClick={() => ctrl.cancel()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{ctrl.state.showForgotPasscodeLink && (
|
||||||
|
<div className="sk-panel-footer">
|
||||||
|
{ctrl.state.forgotPasscode ? (
|
||||||
|
<>
|
||||||
|
<p className="sk-panel-row sk-p">
|
||||||
|
{ctrl.state.hasAccount
|
||||||
|
? 'If you forgot your application passcode, your ' +
|
||||||
|
'only option is to clear your local data from this ' +
|
||||||
|
'device and sign back in to your account.'
|
||||||
|
: 'If you forgot your application passcode, your ' +
|
||||||
|
'only option is to delete your data.'}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
className="sk-panel-row sk-a danger centered"
|
||||||
|
onClick={() => {
|
||||||
|
ctrl.destroyLocalData();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete Local Data
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
className="sk-panel-row sk-a info centered"
|
||||||
|
onClick={() => ctrl.onForgotPasscodeClick()}
|
||||||
|
>
|
||||||
|
Forgot your passcode?
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div className="sk-panel-row"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChallengePrompts({
|
||||||
|
ctrl,
|
||||||
|
initialFocusRef,
|
||||||
|
}: {
|
||||||
|
ctrl: ChallengeModalCtrl;
|
||||||
|
initialFocusRef: Ref<HTMLInputElement>;
|
||||||
|
}) {
|
||||||
|
return ctrl.state.prompts.map((prompt, index) => (
|
||||||
|
<>
|
||||||
|
{/** ProtectionSessionDuration can't just be an input field */}
|
||||||
|
{prompt.validation === ChallengeValidation.ProtectionSessionDuration ? (
|
||||||
|
<div key={prompt.id} className="sk-panel-row">
|
||||||
|
<div className="sk-horizontal-group">
|
||||||
|
<div className="sk-p sk-bold">Remember For</div>
|
||||||
|
{ProtectionSessionDurations.map((option) => (
|
||||||
|
<a
|
||||||
|
className={
|
||||||
|
'sk-a info ' +
|
||||||
|
(option.valueInSeconds === ctrl.state.values[prompt.id]!.value
|
||||||
|
? 'boxed'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
ctrl.onNumberValueChange(prompt, option.valueInSeconds);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div key={prompt.id} className="sk-panel-row">
|
||||||
|
<input
|
||||||
|
className="sk-input contrast"
|
||||||
|
value={ctrl.state.values[prompt.id]!.value as string | number}
|
||||||
|
onChange={(event) => {
|
||||||
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
ctrl.state.values[prompt.id]!.value = value;
|
||||||
|
ctrl.onTextValueChange(prompt);
|
||||||
|
}}
|
||||||
|
onKeyUp={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
ctrl.submit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={index === 0 ? initialFocusRef : undefined}
|
||||||
|
placeholder={prompt.title}
|
||||||
|
type={prompt.secureTextEntry ? 'password' : 'text'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ctrl.state.values[prompt.id]!.invalid && (
|
||||||
|
<div className="sk-panel-row centered">
|
||||||
|
<label className="sk-label danger">
|
||||||
|
Invalid authentication. Please try again.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -80,8 +80,7 @@
|
|||||||
)
|
)
|
||||||
menu-row(
|
menu-row(
|
||||||
action='self.selectedMenuItem(true); self.toggleProtectNote()'
|
action='self.selectedMenuItem(true); self.toggleProtectNote()'
|
||||||
desc=`'Protecting a note will require credentials to view
|
desc=`'Protecting a note will require credentials to view it'`,
|
||||||
it (Manage Privileges via Account menu)'`,
|
|
||||||
label="self.note.protected ? 'Unprotect' : 'Protect'"
|
label="self.note.protected ? 'Unprotect' : 'Protect'"
|
||||||
)
|
)
|
||||||
menu-row(
|
menu-row(
|
||||||
@@ -210,7 +209,7 @@
|
|||||||
on-load='self.onEditorLoad',
|
on-load='self.onEditorLoad',
|
||||||
application='self.application'
|
application='self.application'
|
||||||
)
|
)
|
||||||
textarea#note-text-editor.editable(
|
textarea#note-text-editor.editable.font-editor(
|
||||||
dir='auto',
|
dir='auto',
|
||||||
ng-attr-spellcheck='{{self.state.spellcheck}}',
|
ng-attr-spellcheck='{{self.state.spellcheck}}',
|
||||||
ng-change='self.contentChanged()',
|
ng-change='self.contentChanged()',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STRING_ARCHIVE_LOCKED_ATTEMPT, STRING_SAVING_WHILE_DOCUMENT_HIDDEN, STRING_UNARCHIVE_LOCKED_ATTEMPT } from './../../strings';
|
import { Strings, STRING_ARCHIVE_LOCKED_ATTEMPT, STRING_SAVING_WHILE_DOCUMENT_HIDDEN, STRING_UNARCHIVE_LOCKED_ATTEMPT } from './../../strings';
|
||||||
import { Editor } from '@/ui_models/editor';
|
import { Editor } from '@/ui_models/editor';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { PanelPuppet, WebDirective } from '@/types';
|
import { PanelPuppet, WebDirective } from '@/types';
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
isPayloadSourceRetrieved,
|
isPayloadSourceRetrieved,
|
||||||
isPayloadSourceInternalChange,
|
isPayloadSourceInternalChange,
|
||||||
ContentType,
|
ContentType,
|
||||||
ProtectedAction,
|
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNNote,
|
SNNote,
|
||||||
SNTag,
|
SNTag,
|
||||||
@@ -24,7 +23,7 @@ import { isDesktopApplication } from '@/utils';
|
|||||||
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
|
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
|
||||||
import template from './editor-view.pug';
|
import template from './editor-view.pug';
|
||||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||||
import { AppStateEvent, EventSource } from '@/ui_models/app_state';
|
import { EventSource } from '@/ui_models/app_state';
|
||||||
import {
|
import {
|
||||||
STRING_DELETED_NOTE,
|
STRING_DELETED_NOTE,
|
||||||
STRING_INVALID_NOTE,
|
STRING_INVALID_NOTE,
|
||||||
@@ -48,11 +47,6 @@ const ElementIds = {
|
|||||||
EditorContent: 'editor-content',
|
EditorContent: 'editor-content',
|
||||||
NoteTagsComponentContainer: 'note-tags-component-container'
|
NoteTagsComponentContainer: 'note-tags-component-container'
|
||||||
};
|
};
|
||||||
const Fonts = {
|
|
||||||
DesktopMonospaceFamily: `Menlo,Consolas,'DejaVu Sans Mono',monospace`,
|
|
||||||
WebMonospaceFamily: `monospace`,
|
|
||||||
SansSerifFamily: `inherit`
|
|
||||||
};
|
|
||||||
|
|
||||||
type NoteStatus = {
|
type NoteStatus = {
|
||||||
message?: string
|
message?: string
|
||||||
@@ -85,7 +79,7 @@ type EditorState = {
|
|||||||
* then re-initialized. Used when reloading spellcheck status. */
|
* then re-initialized. Used when reloading spellcheck status. */
|
||||||
textareaUnloading: boolean
|
textareaUnloading: boolean
|
||||||
/** Fields that can be directly mutated by the template */
|
/** Fields that can be directly mutated by the template */
|
||||||
mutable: {}
|
mutable: any
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditorValues = {
|
type EditorValues = {
|
||||||
@@ -98,7 +92,7 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
|||||||
return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
|
return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||||
/** Passed through template */
|
/** Passed through template */
|
||||||
readonly application!: WebApplication
|
readonly application!: WebApplication
|
||||||
readonly editor!: Editor
|
readonly editor!: Editor
|
||||||
@@ -143,7 +137,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
|
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
|
||||||
this.onEditorLoad = () => {
|
this.onEditorLoad = () => {
|
||||||
this.application!.getDesktopService().redoSearch();
|
this.application!.getDesktopService().redoSearch();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
@@ -200,7 +194,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
if (note.lastSyncBegan) {
|
if (note.lastSyncBegan) {
|
||||||
if (note.lastSyncEnd) {
|
if (note.lastSyncEnd) {
|
||||||
if (note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()) {
|
if (note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()) {
|
||||||
this.showSavingStatus()
|
this.showSavingStatus();
|
||||||
} else if (note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()) {
|
} else if (note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()) {
|
||||||
this.showAllChangesSavedStatus();
|
this.showAllChangesSavedStatus();
|
||||||
}
|
}
|
||||||
@@ -248,7 +242,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
case ApplicationEvent.HighLatencySync:
|
case ApplicationEvent.HighLatencySync:
|
||||||
this.setState({ syncTakingTooLong: true });
|
this.setState({ syncTakingTooLong: true });
|
||||||
break;
|
break;
|
||||||
case ApplicationEvent.CompletedFullSync:
|
case ApplicationEvent.CompletedFullSync: {
|
||||||
this.setState({ syncTakingTooLong: false });
|
this.setState({ syncTakingTooLong: false });
|
||||||
const isInErrorState = this.state.saveError;
|
const isInErrorState = this.state.saveError;
|
||||||
/** if we're still dirty, don't change status, a sync is likely upcoming. */
|
/** if we're still dirty, don't change status, a sync is likely upcoming. */
|
||||||
@@ -256,6 +250,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
this.showAllChangesSavedStatus();
|
this.showAllChangesSavedStatus();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ApplicationEvent.FailedSync:
|
case ApplicationEvent.FailedSync:
|
||||||
/**
|
/**
|
||||||
* Only show error status in editor if the note is dirty.
|
* Only show error status in editor if the note is dirty.
|
||||||
@@ -412,7 +407,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
await this.application.changeItem(this.note.uuid, (mutator) => {
|
await this.application.changeItem(this.note.uuid, (mutator) => {
|
||||||
const noteMutator = mutator as NoteMutator;
|
const noteMutator = mutator as NoteMutator;
|
||||||
noteMutator.prefersPlainEditor = false;
|
noteMutator.prefersPlainEditor = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
await this.associateComponentWithCurrentNote(component);
|
await this.associateComponentWithCurrentNote(component);
|
||||||
}
|
}
|
||||||
@@ -471,7 +466,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.addItemAsRelationship(note);
|
mutator.addItemAsRelationship(note);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
if (!this.application.findItem(note.uuid)) {
|
if (!this.application.findItem(note.uuid)) {
|
||||||
this.application.alertService!.alert(
|
this.application.alertService!.alert(
|
||||||
@@ -494,7 +489,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
noteMutator.preview_plain = previewPlain;
|
noteMutator.preview_plain = previewPlain;
|
||||||
noteMutator.preview_html = undefined;
|
noteMutator.preview_html = undefined;
|
||||||
}
|
}
|
||||||
}, isUserModified)
|
}, isUserModified);
|
||||||
if (this.saveTimeout) {
|
if (this.saveTimeout) {
|
||||||
this.$timeout.cancel(this.saveTimeout);
|
this.$timeout.cancel(this.saveTimeout);
|
||||||
}
|
}
|
||||||
@@ -549,7 +544,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
this.statusTimeout = this.$timeout(() => {
|
this.statusTimeout = this.$timeout(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
noteStatus: status
|
noteStatus: status
|
||||||
})
|
});
|
||||||
}, MINIMUM_STATUS_DURATION);
|
}, MINIMUM_STATUS_DURATION);
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -601,10 +596,12 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
this.setMenuState('showOptionsMenu', false);
|
this.setMenuState('showOptionsMenu', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
onTitleFocus() {
|
onTitleFocus() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
onTitleBlur() {
|
onTitleBlur() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -627,7 +624,6 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const run = async () => {
|
|
||||||
if (this.note.locked) {
|
if (this.note.locked) {
|
||||||
this.application.alertService!.alert(
|
this.application.alertService!.alert(
|
||||||
STRING_DELETE_LOCKED_ATTEMPT
|
STRING_DELETE_LOCKED_ATTEMPT
|
||||||
@@ -657,20 +653,6 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
};
|
|
||||||
const requiresPrivilege = await this.application.privilegesService!.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.DeleteNote
|
|
||||||
);
|
|
||||||
if (requiresPrivilege) {
|
|
||||||
this.application.presentPrivilegesModal(
|
|
||||||
ProtectedAction.DeleteNote,
|
|
||||||
() => {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -715,7 +697,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.pinned = !this.note.pinned
|
mutator.pinned = !this.note.pinned;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -726,28 +708,26 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.locked = !this.note.locked
|
mutator.locked = !this.note.locked;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleProtectNote() {
|
async toggleProtectNote() {
|
||||||
this.saveNote(
|
if (this.note.protected) {
|
||||||
true,
|
void this.application.unprotectNote(this.note);
|
||||||
false,
|
} else {
|
||||||
true,
|
const note = await this.application.protectNote(this.note);
|
||||||
(mutator) => {
|
if (note?.protected && !this.application.hasProtectionSources()) {
|
||||||
mutator.protected = !this.note.protected
|
if (await confirmDialog({
|
||||||
|
text: Strings.protectingNoteWithoutProtectionSources,
|
||||||
|
confirmButtonText: Strings.openAccountMenu,
|
||||||
|
confirmButtonStyle: 'info',
|
||||||
|
})) {
|
||||||
|
this.appState.accountMenu.setShow(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
/** Show privileges manager if protection is not yet set up */
|
|
||||||
this.application.privilegesService!.actionHasPrivilegesConfigured(
|
|
||||||
ProtectedAction.ViewProtectedNotes
|
|
||||||
).then((configured) => {
|
|
||||||
if (!configured) {
|
|
||||||
this.application.presentPrivilegesManagementModal();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleNotePreview() {
|
toggleNotePreview() {
|
||||||
@@ -756,7 +736,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.hidePreview = !this.note.hidePreview
|
mutator.hidePreview = !this.note.hidePreview;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -775,7 +755,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.archived = !this.note.archived
|
mutator.archived = !this.note.archived;
|
||||||
},
|
},
|
||||||
/** If we are unarchiving, and we are in the archived tag, close the editor */
|
/** If we are unarchiving, and we are in the archived tag, close the editor */
|
||||||
this.note.archived && this.appState.selectedTag?.isArchiveTag
|
this.note.archived && this.appState.selectedTag?.isArchiveTag
|
||||||
@@ -884,7 +864,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.addItemAsRelationship(note);
|
mutator.addItemAsRelationship(note);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
this.application.sync();
|
this.application.sync();
|
||||||
this.reloadTags();
|
this.reloadTags();
|
||||||
@@ -966,20 +946,18 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadFont() {
|
reloadFont() {
|
||||||
const editor = document.getElementById(
|
const root = document.querySelector(':root') as HTMLElement;
|
||||||
ElementIds.NoteTextEditor
|
const propertyName = '--sn-stylekit-editor-font-family';
|
||||||
);
|
|
||||||
if (!editor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.state.monospaceFont) {
|
if (this.state.monospaceFont) {
|
||||||
if (this.state.isDesktop) {
|
root.style.setProperty(
|
||||||
editor.style.fontFamily = Fonts.DesktopMonospaceFamily;
|
propertyName,
|
||||||
|
'var(--sn-stylekit-monospace-font)'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
editor.style.fontFamily = Fonts.WebMonospaceFamily;
|
root.style.setProperty(
|
||||||
}
|
propertyName,
|
||||||
} else {
|
'var(--sn-stylekit-sans-serif-font)'
|
||||||
editor.style.fontFamily = Fonts.SansSerifFamily;
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,7 +969,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
);
|
);
|
||||||
await this.setState({
|
await this.setState({
|
||||||
[key]: !currentValue
|
[key]: !currentValue
|
||||||
})
|
});
|
||||||
this.reloadFont();
|
this.reloadFont();
|
||||||
|
|
||||||
if (key === PrefKey.EditorSpellcheck) {
|
if (key === PrefKey.EditorSpellcheck) {
|
||||||
@@ -1082,7 +1060,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
(mutator) => {
|
(mutator) => {
|
||||||
mutator.addItemAsRelationship(this.note);
|
mutator.addItemAsRelationship(this.note);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,7 +1119,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
const mutator = m as ComponentMutator;
|
const mutator = m as ComponentMutator;
|
||||||
mutator.removeAssociatedItemId(note.uuid);
|
mutator.removeAssociatedItemId(note.uuid);
|
||||||
mutator.disassociateWithItem(note.uuid);
|
mutator.disassociateWithItem(note.uuid);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async associateComponentWithCurrentNote(component: SNComponent) {
|
async associateComponentWithCurrentNote(component: SNComponent) {
|
||||||
@@ -1150,7 +1128,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
|
|||||||
const mutator = m as ComponentMutator;
|
const mutator = m as ComponentMutator;
|
||||||
mutator.removeDisassociatedItemId(note.uuid);
|
mutator.removeDisassociatedItemId(note.uuid);
|
||||||
mutator.associateWithItem(note.uuid);
|
mutator.associateWithItem(note.uuid);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerKeyboardShortcuts() {
|
registerKeyboardShortcuts() {
|
||||||
|
|||||||
@@ -8,15 +8,10 @@ class EditorGroupViewCtrl {
|
|||||||
private application!: WebApplication
|
private application!: WebApplication
|
||||||
public editors: Editor[] = []
|
public editors: Editor[] = []
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
this.application.editorGroup.addChangeObserver(() => {
|
this.application.editorGroup.addChangeObserver(() => {
|
||||||
this.editors = this.application.editorGroup.editors;
|
this.editors = this.application.editorGroup.editors;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { dateToLocalizedString, preventRefreshing } from '@/utils';
|
|||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
SyncQueueStrategy,
|
SyncQueueStrategy,
|
||||||
ProtectedAction,
|
|
||||||
ContentType,
|
ContentType,
|
||||||
SNComponent,
|
SNComponent,
|
||||||
SNTheme,
|
SNTheme,
|
||||||
@@ -44,7 +43,7 @@ type DockShortcut = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooterViewCtrl extends PureViewCtrl<{}, {
|
class FooterViewCtrl extends PureViewCtrl<unknown, {
|
||||||
outOfSync: boolean;
|
outOfSync: boolean;
|
||||||
hasPasscode: boolean;
|
hasPasscode: boolean;
|
||||||
dataUpgradeAvailable: boolean;
|
dataUpgradeAvailable: boolean;
|
||||||
@@ -63,7 +62,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
public arbitraryStatusMessage?: string
|
public arbitraryStatusMessage?: string
|
||||||
public user?: any
|
public user?: any
|
||||||
private offline = true
|
private offline = true
|
||||||
private showAccountMenu = false
|
public showAccountMenu = false
|
||||||
private didCheckForOffline = false
|
private didCheckForOffline = false
|
||||||
private queueExtReload = false
|
private queueExtReload = false
|
||||||
private reloadInProgress = false
|
private reloadInProgress = false
|
||||||
@@ -76,7 +75,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
private observerRemovers: Array<() => void> = [];
|
private observerRemovers: Array<() => void> = [];
|
||||||
private completedInitialSync = false;
|
private completedInitialSync = false;
|
||||||
private showingDownloadStatus = false;
|
private showingDownloadStatus = false;
|
||||||
private removeBetaWarningListener?: IReactionDisposer;
|
private autorunDisposer?: IReactionDisposer;
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
@@ -104,7 +103,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
this.rootScopeListener2 = undefined;
|
this.rootScopeListener2 = undefined;
|
||||||
(this.closeAccountMenu as any) = undefined;
|
(this.closeAccountMenu as any) = undefined;
|
||||||
(this.toggleSyncResolutionMenu as any) = undefined;
|
(this.toggleSyncResolutionMenu as any) = undefined;
|
||||||
this.removeBetaWarningListener?.();
|
this.autorunDisposer?.();
|
||||||
super.deinit();
|
super.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +115,9 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.loadAccountSwitcherState();
|
this.loadAccountSwitcherState();
|
||||||
this.removeBetaWarningListener = autorun(() => {
|
this.autorunDisposer = autorun(() => {
|
||||||
const showBetaWarning = this.appState.showBetaWarning;
|
const showBetaWarning = this.appState.showBetaWarning;
|
||||||
|
this.showAccountMenu = this.appState.accountMenu.show;
|
||||||
this.setState({
|
this.setState({
|
||||||
showBetaWarning: showBetaWarning,
|
showBetaWarning: showBetaWarning,
|
||||||
showDataUpgrade: !showBetaWarning
|
showDataUpgrade: !showBetaWarning
|
||||||
@@ -207,9 +207,9 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
case AppStateEvent.BeganBackupDownload:
|
case AppStateEvent.BeganBackupDownload:
|
||||||
statusService.setMessage("Saving local backup…");
|
statusService.setMessage("Saving local backup…");
|
||||||
break;
|
break;
|
||||||
case AppStateEvent.EndedBackupDownload:
|
case AppStateEvent.EndedBackupDownload: {
|
||||||
const successMessage = "Successfully saved backup.";
|
const successMessage = "Successfully saved backup.";
|
||||||
const errorMessage = "Unable to save local backup."
|
const errorMessage = "Unable to save local backup.";
|
||||||
statusService.setMessage(data.success ? successMessage : errorMessage);
|
statusService.setMessage(data.success ? successMessage : errorMessage);
|
||||||
|
|
||||||
const twoSeconds = 2000;
|
const twoSeconds = 2000;
|
||||||
@@ -224,6 +224,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async onAppKeyChange() {
|
async onAppKeyChange() {
|
||||||
@@ -255,7 +256,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
if (!this.didCheckForOffline) {
|
if (!this.didCheckForOffline) {
|
||||||
this.didCheckForOffline = true;
|
this.didCheckForOffline = true;
|
||||||
if (this.offline && this.application.getNoteCount() === 0) {
|
if (this.offline && this.application.getNoteCount() === 0) {
|
||||||
this.showAccountMenu = true;
|
this.appState.accountMenu.setShow(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.syncUpdated();
|
this.syncUpdated();
|
||||||
@@ -297,7 +298,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
theme.package_info.dock_icon
|
theme.package_info.dock_icon
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
this.observerRemovers.push(this.application.streamItems(
|
this.observerRemovers.push(this.application.streamItems(
|
||||||
ContentType.Component,
|
ContentType.Component,
|
||||||
@@ -437,7 +438,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
accountMenuPressed() {
|
accountMenuPressed() {
|
||||||
this.showAccountMenu = !this.showAccountMenu;
|
this.appState.accountMenu.toggleShow();
|
||||||
this.closeAllRooms();
|
this.closeAllRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,7 +447,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeAccountMenu() {
|
closeAccountMenu() {
|
||||||
this.showAccountMenu = false;
|
this.appState.accountMenu.setShow(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
lockApp() {
|
lockApp() {
|
||||||
@@ -544,28 +545,9 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async selectRoom(room: SNComponent) {
|
async selectRoom(room: SNComponent) {
|
||||||
const run = () => {
|
|
||||||
this.$timeout(() => {
|
this.$timeout(() => {
|
||||||
this.roomShowState[room.uuid] = !this.roomShowState[room.uuid];
|
this.roomShowState[room.uuid] = !this.roomShowState[room.uuid];
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.roomShowState[room.uuid]) {
|
|
||||||
const requiresPrivilege = await this.application.privilegesService!
|
|
||||||
.actionRequiresPrivilege(
|
|
||||||
ProtectedAction.ManageExtensions
|
|
||||||
);
|
|
||||||
if (requiresPrivilege) {
|
|
||||||
this.application.presentPrivilegesModal(
|
|
||||||
ProtectedAction.ManageExtensions,
|
|
||||||
run
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBetaDialog() {
|
displayBetaDialog() {
|
||||||
@@ -582,7 +564,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
|
|||||||
if (this.application && this.application.authenticationInProgress()) {
|
if (this.application && this.application.authenticationInProgress()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.showAccountMenu = false;
|
this.appState.accountMenu.setShow(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ export { EditorView } from './editor/editor_view';
|
|||||||
export { FooterView } from './footer/footer_view';
|
export { FooterView } from './footer/footer_view';
|
||||||
export { NotesView } from './notes/notes_view';
|
export { NotesView } from './notes/notes_view';
|
||||||
export { TagsView } from './tags/tags_view';
|
export { TagsView } from './tags/tags_view';
|
||||||
export { ChallengeModal } from './challenge_modal/challenge_modal'
|
export { ChallengeModal } from './challenge_modal/challenge_modal';
|
||||||
@@ -5,44 +5,54 @@ export function notePassesFilter(
|
|||||||
showArchived: boolean,
|
showArchived: boolean,
|
||||||
hidePinned: boolean,
|
hidePinned: boolean,
|
||||||
filterText: string
|
filterText: string
|
||||||
) {
|
): boolean {
|
||||||
|
const canShowArchived = showArchived;
|
||||||
let canShowArchived = showArchived;
|
|
||||||
const canShowPinned = !hidePinned;
|
const canShowPinned = !hidePinned;
|
||||||
if (
|
if ((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) {
|
||||||
(note.archived && !canShowArchived) ||
|
|
||||||
(note.pinned && !canShowPinned)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return noteMatchesQuery(note, filterText);
|
if (note.protected) {
|
||||||
|
const match = noteMatchesQuery(note, filterText);
|
||||||
|
/** Only match title to prevent leaking protected note text */
|
||||||
|
return match === Match.Title || match === Match.TitleAndText;
|
||||||
|
} else {
|
||||||
|
return noteMatchesQuery(note, filterText) !== Match.None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteMatchesQuery(
|
enum Match {
|
||||||
note: SNNote,
|
None = 0,
|
||||||
query: string
|
Title = 1,
|
||||||
) {
|
Text = 2,
|
||||||
|
TitleAndText = Title + Text,
|
||||||
|
Uuid = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteMatchesQuery(note: SNNote, query: string): Match {
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
return true;
|
return Match.TitleAndText;
|
||||||
}
|
}
|
||||||
const title = note.safeTitle().toLowerCase();
|
const title = note.safeTitle().toLowerCase();
|
||||||
const text = note.safeText().toLowerCase();
|
const text = note.safeText().toLowerCase();
|
||||||
const lowercaseText = query.toLowerCase();
|
const lowercaseText = query.toLowerCase();
|
||||||
|
const words = lowercaseText.split(' ');
|
||||||
const quotedText = stringBetweenQuotes(lowercaseText);
|
const quotedText = stringBetweenQuotes(lowercaseText);
|
||||||
if (quotedText) {
|
if (quotedText) {
|
||||||
return title.includes(quotedText) || text.includes(quotedText);
|
return (
|
||||||
|
(title.includes(quotedText) ? Match.Title : Match.None) +
|
||||||
|
(text.includes(quotedText) ? Match.Text : Match.None)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (stringIsUuid(lowercaseText)) {
|
if (stringIsUuid(lowercaseText)) {
|
||||||
return note.uuid === lowercaseText;
|
return note.uuid === lowercaseText ? Match.Uuid : Match.None;
|
||||||
}
|
}
|
||||||
const words = lowercaseText.split(" ");
|
|
||||||
const matchesTitle = words.every((word) => {
|
const matchesTitle = words.every((word) => {
|
||||||
return title.indexOf(word) >= 0;
|
return title.indexOf(word) >= 0;
|
||||||
});
|
});
|
||||||
const matchesBody = words.every((word) => {
|
const matchesBody = words.every((word) => {
|
||||||
return text.indexOf(word) >= 0;
|
return text.indexOf(word) >= 0;
|
||||||
});
|
});
|
||||||
return matchesTitle || matchesBody;
|
return (matchesTitle ? Match.Title : 0) + (matchesBody ? Match.Text : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringBetweenQuotes(text: string) {
|
function stringBetweenQuotes(text: string) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#notes-title-bar.section-title-bar
|
#notes-title-bar.section-title-bar
|
||||||
.padded
|
.padded
|
||||||
.section-title-bar-header
|
.section-title-bar-header
|
||||||
.title {{self.state.panelTitle}}
|
.sk-h2.font-semibold.title {{self.state.panelTitle}}
|
||||||
.sk-button.contrast.wide(
|
.sk-button.contrast.wide(
|
||||||
ng-click='self.createNewNote()',
|
ng-click='self.createNewNote()',
|
||||||
title='Create a new note in the selected tag'
|
title='Create a new note in the selected tag'
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
ng-click='self.clearFilterText();',
|
ng-click='self.clearFilterText();',
|
||||||
ng-show='self.state.noteFilter.text'
|
ng-show='self.state.noteFilter.text'
|
||||||
) ✕
|
) ✕
|
||||||
|
no-account-warning(
|
||||||
|
application='self.application'
|
||||||
|
app-state='self.appState'
|
||||||
|
)
|
||||||
#notes-menu-bar.sn-component
|
#notes-menu-bar.sn-component
|
||||||
.sk-app-bar.no-edges
|
.sk-app-bar.no-edges
|
||||||
.left
|
.left
|
||||||
@@ -139,10 +143,12 @@
|
|||||||
.default-preview(
|
.default-preview(
|
||||||
ng-show='!note.preview_html && !note.preview_plain'
|
ng-show='!note.preview_html && !note.preview_plain'
|
||||||
) {{note.text}}
|
) {{note.text}}
|
||||||
.date.faded(ng-show='!self.state.hideDate')
|
.bottom-info.faded(ng-show='!self.state.hideDate || note.protected')
|
||||||
span(ng-show="self.state.sortBy == 'userModifiedDate'")
|
span(ng-if="note.protected")
|
||||||
|
| Protected{{self.state.hideDate ? '' : ' • '}}
|
||||||
|
span(ng-show="!self.state.hideDate && self.state.sortBy == 'userModifiedDate'")
|
||||||
| Modified {{note.updatedAtString || 'Now'}}
|
| Modified {{note.updatedAtString || 'Now'}}
|
||||||
span(ng-show="self.state.sortBy != 'userModifiedDate'")
|
span(ng-show="!self.state.hideDate && self.state.sortBy != 'userModifiedDate'")
|
||||||
| {{note.createdAtString || 'Now'}}
|
| {{note.createdAtString || 'Now'}}
|
||||||
.tags-string(ng-if='!self.state.hideTags && self.state.renderedNotesTags[$index]')
|
.tags-string(ng-if='!self.state.hideTags && self.state.renderedNotesTags[$index]')
|
||||||
.faded {{self.state.renderedNotesTags[$index]}}
|
.faded {{self.state.renderedNotesTags[$index]}}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const DEFAULT_LIST_NUM_NOTES = 20;
|
|||||||
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
|
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
|
||||||
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
|
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
|
||||||
|
|
||||||
class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
|
class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
|
||||||
|
|
||||||
private panelPuppet?: PanelPuppet
|
private panelPuppet?: PanelPuppet
|
||||||
private reloadNotesPromise?: any
|
private reloadNotesPromise?: any
|
||||||
@@ -410,7 +410,7 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
|
|||||||
if (activeNote && activeNote.conflictOf) {
|
if (activeNote && activeNote.conflictOf) {
|
||||||
this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => {
|
this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => {
|
||||||
mutator.conflictOf = undefined;
|
mutator.conflictOf = undefined;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (this.isFiltering()) {
|
if (this.isFiltering()) {
|
||||||
this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
|
this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
|
||||||
@@ -576,12 +576,6 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
|
|||||||
class: 'warning'
|
class: 'warning'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (note.protected) {
|
|
||||||
flags.push({
|
|
||||||
text: "Protected",
|
|
||||||
class: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (note.locked) {
|
if (note.locked) {
|
||||||
flags.push({
|
flags.push({
|
||||||
text: "Locked",
|
text: "Locked",
|
||||||
@@ -641,7 +635,7 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
|
|||||||
selectNextNote() {
|
selectNextNote() {
|
||||||
const displayableNotes = this.displayableNotes();
|
const displayableNotes = this.displayableNotes();
|
||||||
const currentIndex = displayableNotes.findIndex((candidate) => {
|
const currentIndex = displayableNotes.findIndex((candidate) => {
|
||||||
return candidate.uuid === this.activeEditorNote!.uuid
|
return candidate.uuid === this.activeEditorNote!.uuid;
|
||||||
});
|
});
|
||||||
if (currentIndex + 1 < displayableNotes.length) {
|
if (currentIndex + 1 < displayableNotes.length) {
|
||||||
this.selectNote(displayableNotes[currentIndex + 1]);
|
this.selectNote(displayableNotes[currentIndex + 1]);
|
||||||
@@ -798,7 +792,7 @@ class NotesViewCtrl extends PureViewCtrl<{}, NotesState> {
|
|||||||
],
|
],
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
const searchBar = this.getSearchBar();
|
const searchBar = this.getSearchBar();
|
||||||
if (searchBar) { searchBar.focus(); };
|
if (searchBar) { searchBar.focus(); }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
.tag-info
|
.tag-info
|
||||||
.title(ng-if="!tag.errorDecrypting") {{tag.title}}
|
.title(ng-if="!tag.errorDecrypting") {{tag.title}}
|
||||||
.count(ng-show='tag.isAllTag') {{self.state.noteCounts[tag.uuid]}}
|
.count(ng-show='tag.isAllTag') {{self.state.noteCounts[tag.uuid]}}
|
||||||
.danger.small-text.bold(ng-show='tag.conflictOf') Conflicted Copy
|
.danger.small-text.font-bold(ng-show='tag.conflictOf') Conflicted Copy
|
||||||
.danger.small-text.bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys
|
.danger.small-text.font-bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys
|
||||||
.info.small-text.bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys
|
.info.small-text.font-bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys
|
||||||
.tags-title-section.section-title-bar
|
.tags-title-section.section-title-bar
|
||||||
.section-title-bar-header
|
.section-title-bar-header
|
||||||
.sk-h3.title
|
.sk-h3.title
|
||||||
@@ -52,9 +52,9 @@
|
|||||||
spellcheck='false'
|
spellcheck='false'
|
||||||
)
|
)
|
||||||
.count {{self.state.noteCounts[tag.uuid]}}
|
.count {{self.state.noteCounts[tag.uuid]}}
|
||||||
.danger.small-text.bold(ng-show='tag.conflictOf') Conflicted Copy
|
.danger.small-text.font-bold(ng-show='tag.conflictOf') Conflicted Copy
|
||||||
.danger.small-text.bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys
|
.danger.small-text.font-bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys
|
||||||
.info.small-text.bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys
|
.info.small-text.font-bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys
|
||||||
.menu(ng-show='self.state.selectedTag == tag')
|
.menu(ng-show='self.state.selectedTag == tag')
|
||||||
a.item(ng-click='self.selectedRenameTag(tag)' ng-show='!self.state.editingTag') Rename
|
a.item(ng-click='self.selectedRenameTag(tag)' ng-show='!self.state.editingTag') Rename
|
||||||
a.item(ng-click='self.saveTag($event, tag)' ng-show='self.state.editingTag') Save
|
a.item(ng-click='self.saveTag($event, tag)' ng-show='self.state.editingTag') Save
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type TagState = {
|
|||||||
templateTag?: SNTag
|
templateTag?: SNTag
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||||
|
|
||||||
/** Passed through template */
|
/** Passed through template */
|
||||||
readonly application!: WebApplication
|
readonly application!: WebApplication
|
||||||
@@ -136,7 +136,7 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedTag: matchingTag
|
selectedTag: matchingTag
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,14 +186,14 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
|||||||
const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag)
|
const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag)
|
||||||
.filter((note) => {
|
.filter((note) => {
|
||||||
return !note.archived && !note.trashed;
|
return !note.archived && !note.trashed;
|
||||||
})
|
});
|
||||||
noteCounts[tag.uuid] = notes.length;
|
noteCounts[tag.uuid] = notes.length;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const notes = this.application.referencesForItem(tag, ContentType.Note)
|
const notes = this.application.referencesForItem(tag, ContentType.Note)
|
||||||
.filter((note) => {
|
.filter((note) => {
|
||||||
return !note.archived && !note.trashed;
|
return !note.archived && !note.trashed;
|
||||||
})
|
});
|
||||||
noteCounts[tag.uuid] = notes.length;
|
noteCounts[tag.uuid] = notes.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
|||||||
if (tag.conflictOf) {
|
if (tag.conflictOf) {
|
||||||
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
|
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
|
||||||
mutator.conflictOf = undefined;
|
mutator.conflictOf = undefined;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
this.application.getAppState().setSelectedTag(tag);
|
this.application.getAppState().setSelectedTag(tag);
|
||||||
}
|
}
|
||||||
@@ -326,7 +326,7 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
|||||||
"A tag with this name already exists."
|
"A tag with this name already exists."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
await this.application.changeAndSaveItem<TagMutator>(tag.uuid, (mutator) => {
|
await this.application.changeAndSaveItem<TagMutator>(tag.uuid, (mutator) => {
|
||||||
mutator.title = newTitle;
|
mutator.title = newTitle;
|
||||||
});
|
});
|
||||||
@@ -350,7 +350,7 @@ class TagsViewCtrl extends PureViewCtrl<{}, TagState> {
|
|||||||
"A tag with this name already exists."
|
"A tag with this name already exists."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
const insertedTag = await this.application.insertItem(newTag);
|
const insertedTag = await this.application.insertItem(newTag);
|
||||||
const changedTag = await this.application.changeItem<TagMutator>(insertedTag.uuid, (m) => {
|
const changedTag = await this.application.changeItem<TagMutator>(insertedTag.uuid, (m) => {
|
||||||
m.title = newTitle;
|
m.title = newTitle;
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ $heading-height: 75px;
|
|||||||
&:disabled {
|
&:disabled {
|
||||||
color: var(--sn-stylekit-editor-foreground-color);
|
color: var(--sn-stylekit-editor-foreground-color);
|
||||||
}
|
}
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +120,7 @@ $heading-height: 75px;
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,6 @@ $heading-height: 75px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editable {
|
.editable {
|
||||||
font-family: monospace;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--sn-stylekit-editor-background-color);
|
background-color: var(--sn-stylekit-editor-background-color);
|
||||||
@@ -182,3 +185,7 @@ $heading-height: 75px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#note-text-editor:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
src: url('../fonts/ionicons.eot?v=2.0.0');
|
src: url('../fonts/ionicons.eot?v=2.0.0');
|
||||||
src: url('../fonts/ionicons.eot?v=2.0.1#iefix') format('embedded-opentype'),
|
src: url('../fonts/ionicons.eot?v=2.0.1#iefix') format('embedded-opentype'),
|
||||||
url('../fonts/ionicons.ttf?v=2.0.1') format('truetype'),
|
url('../fonts/ionicons.ttf?v=2.0.1') format('truetype'),
|
||||||
url('../fonts/ionicons.woff?v=2.0.1') format('woff'),
|
url('../fonts/ionicons.woff?v=2.0.1') format('woff');
|
||||||
url('../fonts/ionicons.svg?v=2.0.1#Ionicons') format('svg');
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ a {
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
color: var(--sn-stylekit-paragraph-text-color);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-ui-view {
|
.main-ui-view {
|
||||||
|
|||||||
@@ -42,55 +42,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#privileges-modal {
|
|
||||||
min-width: 400px;
|
|
||||||
max-width: 700px;
|
|
||||||
|
|
||||||
.sk-panel-header {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
padding: 1.1rem 2rem;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0px;
|
|
||||||
border-color: var(--sn-stylekit-contrast-border-color);
|
|
||||||
background-color: var(--sn-stylekit-background-color);
|
|
||||||
color: var(--sn-stylekit-contrast-foreground-color);
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 6px 13px;
|
|
||||||
border: 1px solid var(--sn-stylekit-contrast-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(2n) {
|
|
||||||
background-color: var(--sn-stylekit-contrast-background-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priv-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#item-preview-modal {
|
#item-preview-modal {
|
||||||
> .sk-modal-content {
|
> .sk-modal-content {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
|
|||||||
@@ -30,13 +30,14 @@
|
|||||||
#notes-title-bar {
|
#notes-title-bar {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: var(--sn-stylekit-font-size-h1);
|
|
||||||
|
|
||||||
.section-title-bar-header .title {
|
.section-title-bar-header .title {
|
||||||
font-size: var(--sn-stylekit-font-size-h3);
|
|
||||||
font-weight: 600;
|
|
||||||
width: calc(90% - 45px);
|
width: calc(90% - 45px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: var(--sn-stylekit-font-size-p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#notes-menu-bar {
|
#notes-menu-bar {
|
||||||
@@ -67,12 +68,6 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
border-color: var(--sn-stylekit-info-color);
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-clear-button {
|
#search-clear-button {
|
||||||
@@ -86,7 +81,8 @@
|
|||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
|
||||||
transition: background-color 0.15s linear;
|
transition: background-color 0.15s linear;
|
||||||
@@ -130,7 +126,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .date {
|
> .bottom-info {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-dialog-content] {
|
[data-reach-dialog-content] {
|
||||||
|
width: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
|
min-width: 400px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
.sessions-modal {
|
.sessions-modal {
|
||||||
|
min-width: 40vw;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
h2, ul, p {
|
h2, ul, p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
16
app/assets/stylesheets/_sn.scss
Normal file
16
app/assets/stylesheets/_sn.scss
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* Generic UI controls that have yet to be extracted into Stylekit */
|
||||||
|
|
||||||
|
.sn-btn {
|
||||||
|
@extend .border-0;
|
||||||
|
@extend .bg-main;
|
||||||
|
@extend .cursor-pointer;
|
||||||
|
@extend .capitalize;
|
||||||
|
@extend .font-bold;
|
||||||
|
@extend .py-2;
|
||||||
|
@extend .px-3;
|
||||||
|
@extend .rounded;
|
||||||
|
@extend .text-info-contrast;
|
||||||
|
@extend .text-sm;
|
||||||
|
|
||||||
|
@extend .hover\:brightness-130;
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
.sk-panel {
|
.sk-panel {
|
||||||
.sk-panel-header {
|
.sk-panel-header {
|
||||||
.close-button {
|
.close-button {
|
||||||
|
border-radius: var(--sn-stylekit-general-border-radius);
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -88,5 +89,15 @@ button.sk-a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
*:focus {
|
*:focus {
|
||||||
outline: solid var(--sn-stylekit-info-color) 2px;
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px var(--sn-stylekit-info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
box-shadow: 0 0 0 1px var(--sn-stylekit-info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-button:focus, button:focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--sn-stylekit-background-color),
|
||||||
|
0 0 0 4px var(--sn-stylekit-info-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
box-shadow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -57,35 +57,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectable {
|
|
||||||
user-select: text !important;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-5 {
|
|
||||||
margin-top: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-10 {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-5 {
|
|
||||||
margin-right: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-8 {
|
|
||||||
margin-right: 8px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faded {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-align {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
@@ -94,19 +65,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrap {
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.medium-padding {
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.normal {
|
.normal {
|
||||||
font-weight: normal !important;
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
@@ -118,3 +76,151 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
|||||||
.medium-text {
|
.medium-text {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.faded {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-start {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-start {
|
||||||
|
justify-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-0 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: .25rem;
|
||||||
|
}
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: .75rem;
|
||||||
|
}
|
||||||
|
.mt-5 {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-0 {
|
||||||
|
padding: 0rem;
|
||||||
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-3 {
|
||||||
|
padding-left: .75rem;
|
||||||
|
padding-right: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-2 {
|
||||||
|
padding-top: .5rem;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-0 {
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded {
|
||||||
|
border-radius: var(--sn-stylekit-general-border-radius);
|
||||||
|
}
|
||||||
|
.rounded-md {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-main {
|
||||||
|
background-color: var(--sn-stylekit-info-color);
|
||||||
|
}
|
||||||
|
.bg-transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capitalize {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-start-1 {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
.col-start-2 {
|
||||||
|
grid-column-start: 2;
|
||||||
|
}
|
||||||
|
.col-end-3 {
|
||||||
|
grid-column-end: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-neutral {
|
||||||
|
color: var(--sn-stylekit-neutral-color)
|
||||||
|
}
|
||||||
|
.hover\:color-info:hover {
|
||||||
|
color: var(--sn-stylekit-info-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:brightness-130:hover {
|
||||||
|
filter: brightness(130%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-current {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-editor {
|
||||||
|
font-family: var(--sn-stylekit-editor-font-family);
|
||||||
|
}
|
||||||
|
.font-semibold {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.grid-template-cols-1fr {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-start-1 {
|
||||||
|
grid-row-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable {
|
||||||
|
user-select: text !important;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-sm {
|
||||||
|
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.04), 0px 1px 4px 0px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: var(--sn-stylekit-font-size-h5);;
|
||||||
|
}
|
||||||
|
.text-info-contrast {
|
||||||
|
color: var(--sn-stylekit-info-contrast-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,3 +11,4 @@
|
|||||||
@import "ionicons";
|
@import "ionicons";
|
||||||
@import "reach-sub";
|
@import "reach-sub";
|
||||||
@import "sessions-modal";
|
@import "sessions-modal";
|
||||||
|
@import "sn";
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
.sk-notification-title.sk-panel-row.padded-row Advanced Options
|
.sk-notification-title.sk-panel-row.padded-row Advanced Options
|
||||||
.bordered-row.padded-row
|
.bordered-row.padded-row
|
||||||
label.sk-label Sync Server Domain
|
label.sk-label Sync Server Domain
|
||||||
input.sk-input.mt-5.sk-base(
|
input.sk-input.sk-base(
|
||||||
name='server',
|
name='server',
|
||||||
ng-model='self.state.formData.url',
|
ng-model='self.state.formData.url',
|
||||||
ng-change='self.onHostInputChange()'
|
ng-change='self.onHostInputChange()'
|
||||||
@@ -156,18 +156,26 @@
|
|||||||
) Change Password
|
) Change Password
|
||||||
a.sk-a.info.sk-panel-row.condensed(
|
a.sk-a.info.sk-panel-row.condensed(
|
||||||
ng-click="self.openSessionsModal()"
|
ng-click="self.openSessionsModal()"
|
||||||
ng-if="self.state.showSessions"
|
|
||||||
) Manage Sessions
|
) Manage Sessions
|
||||||
a.sk-a.info.sk-panel-row.condensed(
|
|
||||||
ng-click="self.openPrivilegesModal('')",
|
|
||||||
ng-show='self.state.user'
|
|
||||||
) Manage Privileges
|
|
||||||
.sk-panel-section
|
.sk-panel-section
|
||||||
.sk-panel-section-title Encryption
|
.sk-panel-section-title Encryption
|
||||||
.sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled')
|
.sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled')
|
||||||
| {{self.encryptionStatusForNotes()}}
|
| {{self.encryptionStatusForNotes()}}
|
||||||
p.sk-p
|
p.sk-p
|
||||||
| {{self.state.encryptionStatusString}}
|
| {{self.state.encryptionStatusString}}
|
||||||
|
.sk-panel-section(ng-if="self.hasProtections()")
|
||||||
|
.sk-panel-section-title Protections
|
||||||
|
.sk-panel-section-subtitle.info(ng-if="self.state.protectionsDisabledUntil")
|
||||||
|
| Protections are disabled until {{self.state.protectionsDisabledUntil}}
|
||||||
|
.sk-panel-section-subtitle.info(ng-if="!self.state.protectionsDisabledUntil")
|
||||||
|
| Protections are enabled
|
||||||
|
p.sk-p
|
||||||
|
| Actions like viewing protected notes, exporting decrypted backups,
|
||||||
|
| or revoking an active session, require additional authentication
|
||||||
|
| like entering your account password or application passcode.
|
||||||
|
.sk-panel-row(ng-if="self.state.protectionsDisabledUntil")
|
||||||
|
button.sk-button.info(ng-click="self.enableProtections()")
|
||||||
|
span.sk-label.capitalize Enable protections
|
||||||
.sk-panel-section
|
.sk-panel-section
|
||||||
.sk-panel-section-title Passcode Lock
|
.sk-panel-section-title Passcode Lock
|
||||||
div(ng-if='!self.state.hasPasscode')
|
div(ng-if='!self.state.hasPasscode')
|
||||||
@@ -180,6 +188,8 @@
|
|||||||
p.sk-p
|
p.sk-p
|
||||||
| Add a passcode to lock the application and
|
| Add a passcode to lock the application and
|
||||||
| encrypt on-device key storage.
|
| encrypt on-device key storage.
|
||||||
|
p(ng-if='self.state.keyStorageInfo')
|
||||||
|
| {{self.state.keyStorageInfo}}
|
||||||
div(ng-if='!self.state.canAddPasscode')
|
div(ng-if='!self.state.canAddPasscode')
|
||||||
p.sk-p
|
p.sk-p
|
||||||
| Adding a passcode is not supported in temporary sessions. Please sign
|
| Adding a passcode is not supported in temporary sessions. Please sign
|
||||||
@@ -208,8 +218,7 @@
|
|||||||
ng-click='self.state.formData.showPasscodeForm = false'
|
ng-click='self.state.formData.showPasscodeForm = false'
|
||||||
) Cancel
|
) Cancel
|
||||||
div(ng-if='self.state.hasPasscode && !self.state.formData.showPasscodeForm')
|
div(ng-if='self.state.hasPasscode && !self.state.formData.showPasscodeForm')
|
||||||
.sk-p
|
.sk-panel-section-subtitle.info Passcode lock is enabled
|
||||||
| Passcode lock is enabled.
|
|
||||||
.sk-notification.contrast
|
.sk-notification.contrast
|
||||||
.sk-notification-title Options
|
.sk-notification-title Options
|
||||||
.sk-notification-text
|
.sk-notification-text
|
||||||
@@ -226,10 +235,6 @@
|
|||||||
| {{option.label}}
|
| {{option.label}}
|
||||||
.sk-p The autolock timer begins when the window or tab loses focus.
|
.sk-p The autolock timer begins when the window or tab loses focus.
|
||||||
.sk-panel-row
|
.sk-panel-row
|
||||||
a.sk-a.info.sk-panel-row.condensed(
|
|
||||||
ng-click="self.openPrivilegesModal('')",
|
|
||||||
ng-show='!self.state.user'
|
|
||||||
) Manage Privileges
|
|
||||||
a.sk-a.info.sk-panel-row.condensed(
|
a.sk-a.info.sk-panel-row.condensed(
|
||||||
ng-click='self.changePasscodePressed()'
|
ng-click='self.changePasscodePressed()'
|
||||||
) Change Passcode
|
) Change Passcode
|
||||||
@@ -274,33 +279,22 @@
|
|||||||
span(ng-if='self.isDesktopApplication()')
|
span(ng-if='self.isDesktopApplication()')
|
||||||
| Backups are automatically created on desktop and can be managed
|
| Backups are automatically created on desktop and can be managed
|
||||||
| via the "Backups" top-level menu.
|
| via the "Backups" top-level menu.
|
||||||
#import-password-request(ng-if='self.state.importData.requestPassword')
|
|
||||||
form.sk-panel-form.stretch(ng-submit='self.submitImportPassword()')
|
|
||||||
p Enter the account password associated with the import file.
|
|
||||||
input.sk-input.contrast.mt-5(
|
|
||||||
autofocus='true',
|
|
||||||
ng-model='self.state.importData.password',
|
|
||||||
placeholder='Enter File Account Password',
|
|
||||||
type='password'
|
|
||||||
)
|
|
||||||
.sk-button-group.stretch.sk-panel-row.form-submit
|
|
||||||
button.sk-button.info(type='submit')
|
|
||||||
.sk-label Decrypt & Import
|
|
||||||
p
|
|
||||||
| Importing from backup will not overwrite existing data,
|
|
||||||
| but instead create a duplicate of any differing data.
|
|
||||||
p
|
|
||||||
| If you'd like to import only a selection of items instead of
|
|
||||||
| the whole file, please use the Batch Manager extension.
|
|
||||||
.sk-panel-row
|
.sk-panel-row
|
||||||
.sk-spinner.small.info(ng-if='self.state.importData.loading')
|
.sk-spinner.small.info(ng-if='self.state.importData.loading')
|
||||||
.sk-panel-section
|
.sk-panel-section
|
||||||
.sk-panel-section-title Error Reporting
|
.sk-panel-section-title Error Reporting
|
||||||
.sk-panel-section-subtitle.info
|
.sk-panel-section-subtitle.info
|
||||||
| Automatic error reporting is {{ self.state.errorReportingEnabled ? 'enabled.' : 'disabled.' }}
|
| Automatic error reporting is {{ self.state.errorReportingEnabled ? 'enabled' : 'disabled' }}
|
||||||
p.sk-p
|
p.sk-p
|
||||||
| Help us improve Standard Notes by automatically submitting
|
| Help us improve Standard Notes by automatically submitting
|
||||||
| anonymized error reports.
|
| anonymized error reports.
|
||||||
|
p.sk-p.selectable(ng-if="self.state.errorReportingId")
|
||||||
|
| Your random identifier is
|
||||||
|
strong {{ self.state.errorReportingId }}
|
||||||
|
p.sk-p(ng-if="self.state.errorReportingId")
|
||||||
|
| Disabling error reporting will remove that identifier from your
|
||||||
|
| local storage, and a new identifier will be created should you
|
||||||
|
| decide to enable error reporting again in the future.
|
||||||
.sk-panel-row
|
.sk-panel-row
|
||||||
button(ng-click="self.toggleErrorReportingEnabled()").sk-button.info
|
button(ng-click="self.toggleErrorReportingEnabled()").sk-button.info
|
||||||
span.sk-label {{ self.state.errorReportingEnabled ? 'Disable' : 'Enable'}} Error Reporting
|
span.sk-label {{ self.state.errorReportingEnabled ? 'Disable' : 'Enable'}} Error Reporting
|
||||||
|
|||||||
@@ -21,5 +21,5 @@
|
|||||||
target='_blank'
|
target='_blank'
|
||||||
) https://standardnotes.org/permissions.
|
) https://standardnotes.org/permissions.
|
||||||
.sk-panel-footer
|
.sk-panel-footer
|
||||||
.sk-button.info.big.block.bold(ng-click='ctrl.accept()')
|
.sk-button.info.big.block.font-bold(ng-click='ctrl.accept()')
|
||||||
.sk-label Continue
|
.sk-label Continue
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
.sk-modal-background(ng-click="ctrl.cancel()")
|
|
||||||
#privileges-modal.sk-modal-content
|
|
||||||
.sn-component
|
|
||||||
.sk-panel
|
|
||||||
.sk-panel-header
|
|
||||||
.sk-panel-header-title Authentication Required
|
|
||||||
a.close-button.info(ng-click="ctrl.cancel()") Cancel
|
|
||||||
.sk-panel-content
|
|
||||||
.sk-panel-section
|
|
||||||
div(ng-repeat="credential in ctrl.requiredCredentials")
|
|
||||||
.sk-p.sk-bold.sk-panel-row
|
|
||||||
strong {{ctrl.promptForCredential(credential)}}
|
|
||||||
.sk-panel-row
|
|
||||||
input.sk-input.contrast(
|
|
||||||
ng-model="ctrl.authParameters[credential]"
|
|
||||||
should-focus="$index == 0"
|
|
||||||
sn-autofocus="true"
|
|
||||||
sn-enter="ctrl.submit()"
|
|
||||||
type="password"
|
|
||||||
)
|
|
||||||
.sk-panel-row
|
|
||||||
label.sk-label.danger(
|
|
||||||
ng-if="ctrl.isCredentialInFailureState(credential)"
|
|
||||||
) Invalid authentication. Please try again.
|
|
||||||
.sk-panel-row
|
|
||||||
.sk-panel-row
|
|
||||||
.sk-horizontal-group
|
|
||||||
.sk-p.sk-bold Remember For
|
|
||||||
a.sk-a.info(
|
|
||||||
ng-repeat="option in ctrl.sessionLengthOptions"
|
|
||||||
ng-class="{'boxed' : option.value == ctrl.selectedSessionLength}"
|
|
||||||
ng-click="ctrl.selectSessionLength(option.value)"
|
|
||||||
)
|
|
||||||
| {{option.label}}
|
|
||||||
.sk-panel-footer.extra-padding
|
|
||||||
.sk-button.info.big.block.bold(ng-click="ctrl.submit()")
|
|
||||||
.sk-label Submit
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
.sk-modal-background(ng-click='ctrl.cancel()')
|
|
||||||
#privileges-modal.sk-modal-content
|
|
||||||
.sn-component
|
|
||||||
.sk-panel
|
|
||||||
.sk-panel-header
|
|
||||||
.sk-panel-header-title Manage Privileges
|
|
||||||
a.sk-a.close-button.info(ng-click='ctrl.cancel()') Done
|
|
||||||
.sk-panel-content
|
|
||||||
.sk-panel-section
|
|
||||||
table.sk-table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
th
|
|
||||||
th(ng-repeat='cred in ctrl.availableCredentials')
|
|
||||||
.priv-header
|
|
||||||
strong {{ctrl.credentialDisplayInfo[cred].label}}
|
|
||||||
.sk-p.font-small(
|
|
||||||
ng-show='!ctrl.credentialDisplayInfo[cred].availability',
|
|
||||||
style='margin-top: 2px'
|
|
||||||
) Not Configured
|
|
||||||
tbody
|
|
||||||
tr(ng-repeat='action in ctrl.availableActions')
|
|
||||||
td
|
|
||||||
.sk-p {{ctrl.displayInfoForAction(action)}}
|
|
||||||
th(ng-repeat='credential in ctrl.availableCredentials')
|
|
||||||
input(
|
|
||||||
ng-checked='ctrl.isCredentialRequiredForAction(action, credential)',
|
|
||||||
ng-click='ctrl.checkboxValueChanged(action, credential)',
|
|
||||||
ng-disabled='!ctrl.credentialDisplayInfo[credential].availability',
|
|
||||||
type='checkbox'
|
|
||||||
)
|
|
||||||
.sk-panel-section(ng-if='ctrl.sessionExpirey && !ctrl.sessionExpired')
|
|
||||||
.sk-p.sk-panel-row
|
|
||||||
| You will not be asked to authenticate until {{ctrl.sessionExpirey}}.
|
|
||||||
a.sk-a.sk-panel-row.info(ng-click='ctrl.clearSession()') Clear Session
|
|
||||||
.sk-panel-footer
|
|
||||||
.sk-h2.sk-bold About Privileges
|
|
||||||
.sk-panel-section.no-bottom-pad
|
|
||||||
.sk-panel-row
|
|
||||||
.text-content
|
|
||||||
.sk-p
|
|
||||||
| Privileges represent interface level authentication for accessing
|
|
||||||
| certain items and features. Note that when your application is unlocked,
|
|
||||||
| your data exists in temporary memory in an unencrypted state.
|
|
||||||
| Privileges are meant to protect against unwanted access in the event of
|
|
||||||
| an unlocked application, but do not affect data encryption state.
|
|
||||||
p.sk-p
|
|
||||||
| Privileges sync across your other devices; however, note that if you
|
|
||||||
| require an "Application Passcode" privilege, and another device does not have
|
|
||||||
| an application passcode set up, the application passcode requirement will be ignored
|
|
||||||
| on that device.
|
|
||||||
1
app/extensions/extensions-manager
Submodule
1
app/extensions/extensions-manager
Submodule
Submodule app/extensions/extensions-manager added at 206c1da0a4
62
package.json
62
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "standard-notes-web",
|
"name": "standard-notes-web",
|
||||||
"version": "3.5.19",
|
"version": "3.6.0-beta01",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "yarn submodules && yarn install",
|
"setup": "yarn submodules && yarn install",
|
||||||
"start": "webpack-dev-server --progress --config webpack.dev.js",
|
"start": "webpack-dev-server --config webpack.dev.js",
|
||||||
"watch": "webpack -w --config webpack.dev.js",
|
"watch": "webpack -w --config webpack.dev.js",
|
||||||
"watch:desktop": "webpack -w --config webpack.dev.js --env.platform='desktop'",
|
"watch:desktop": "webpack -w --config webpack.dev.js --env.platform='desktop'",
|
||||||
"bundle": "webpack --config webpack.prod.js && yarn tsc",
|
"bundle": "webpack --config webpack.prod.js && yarn tsc",
|
||||||
@@ -16,64 +16,60 @@
|
|||||||
"bundle:desktop:beta": "webpack --config webpack.prod.js --env.platform='desktop' --env.public_beta='true'",
|
"bundle:desktop:beta": "webpack --config webpack.prod.js --env.platform='desktop' --env.public_beta='true'",
|
||||||
"build": "bundle install && yarn install --pure-lockfile && bundle exec rails assets:precompile && yarn bundle",
|
"build": "bundle install && yarn install --pure-lockfile && bundle exec rails assets:precompile && yarn bundle",
|
||||||
"submodules": "git submodule update --init --force",
|
"submodules": "git submodule update --init --force",
|
||||||
"lint": "eslint --fix app/assets/javascripts/**/*.js",
|
"lint": "eslint --fix app/assets/javascripts",
|
||||||
"tsc": "tsc --project app/assets/javascripts/tsconfig.json"
|
"tsc": "tsc --project app/assets/javascripts/tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.16",
|
||||||
"@babel/plugin-transform-react-jsx": "^7.12.10",
|
"@babel/plugin-transform-react-jsx": "^7.12.16",
|
||||||
"@babel/preset-env": "^7.12.10",
|
"@babel/preset-env": "^7.12.16",
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.16",
|
||||||
|
"@svgr/webpack": "^5.5.0",
|
||||||
"@types/angular": "^1.8.0",
|
"@types/angular": "^1.8.0",
|
||||||
"@types/chai": "^4.2.11",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/lodash": "^4.14.149",
|
|
||||||
"@types/mocha": "^7.0.2",
|
|
||||||
"@types/pug": "^2.0.4",
|
"@types/pug": "^2.0.4",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||||
"@typescript-eslint/parser": "^2.23.0",
|
"@typescript-eslint/parser": "^4.14.0",
|
||||||
"angular": "^1.8.2",
|
"angular": "^1.8.2",
|
||||||
"apply-loader": "^2.0.0",
|
"apply-loader": "^2.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
|
"babel-loader": "^8.2.2",
|
||||||
"babel-plugin-angularjs-annotate": "^0.10.0",
|
"babel-plugin-angularjs-annotate": "^0.10.0",
|
||||||
"chai": "^4.2.0",
|
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"css-loader": "^3.4.2",
|
"css-loader": "^3.4.2",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.18.0",
|
||||||
"eslint-config-prettier": "^6.10.0",
|
"eslint-config-prettier": "^7.2.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
|
||||||
"file-loader": "^5.1.0",
|
"file-loader": "^5.1.0",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-webpack-plugin": "^4.3.0",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.20",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"mocha": "^7.1.0",
|
|
||||||
"ng-cache-loader": "0.0.26",
|
"ng-cache-loader": "0.0.26",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"pug": "^2.0.4",
|
"pug": "^2.0.4",
|
||||||
"pug-loader": "^2.4.0",
|
"pug-loader": "^2.4.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"sn-stylekit": "2.1.0",
|
"sn-stylekit": "^2.2.0",
|
||||||
"ts-loader": "^8.0.12",
|
"ts-loader": "^8.0.17",
|
||||||
"typescript": "^4.1.3",
|
"typescript": "^4.1.5",
|
||||||
"typescript-eslint": "0.0.1-alpha.0",
|
"typescript-eslint": "0.0.1-alpha.0",
|
||||||
"webpack": "^4.44.1",
|
"webpack": "^4.44.1",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^3.3.12",
|
||||||
"webpack-dev-server": "^3.11.0",
|
"webpack-dev-server": "^3.11.2",
|
||||||
"webpack-merge": "^4.2.2"
|
"webpack-merge": "^4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bugsnag/js": "^7.5.1",
|
"@bugsnag/js": "^7.6.0",
|
||||||
"@reach/alert": "^0.12.1",
|
"@reach/alert": "^0.13.0",
|
||||||
"@reach/alert-dialog": "^0.12.1",
|
"@reach/alert-dialog": "^0.13.0",
|
||||||
"@reach/dialog": "^0.12.1",
|
"@reach/dialog": "^0.13.0",
|
||||||
"@standardnotes/sncrypto-web": "^1.2.9",
|
"@standardnotes/sncrypto-web": "^1.2.10",
|
||||||
"@standardnotes/snjs": "^2.0.38",
|
"@standardnotes/snjs": "^2.0.61",
|
||||||
"babel-loader": "^8.2.2",
|
"mobx": "^6.1.6",
|
||||||
"mobx": "^6.0.4",
|
"preact": "^10.5.12"
|
||||||
"preact": "^10.5.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
vendor/extensions/extensions-manager
vendored
Submodule
1
vendor/extensions/extensions-manager
vendored
Submodule
Submodule vendor/extensions/extensions-manager added at 206c1da0a4
@@ -71,7 +71,11 @@ module.exports = (
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
test: /\.svg$/,
|
||||||
|
use: ['@svgr/webpack'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
|
|||||||
Reference in New Issue
Block a user