mirror of
https://github.com/standardnotes/app
synced 2026-09-16 09:45:56 -04:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c18feeffe | ||
|
|
32c3b98293 | ||
|
|
fe07c58e98 | ||
|
|
9e1bfc8c73 | ||
|
|
3c563ae4c3 | ||
|
|
ac121265a9 | ||
|
|
fc137a7e7b | ||
|
|
8f4ec469ba | ||
|
|
904d8fd1e9 | ||
|
|
877d46d8eb | ||
|
|
1bed27562a | ||
|
|
0203cf50ea | ||
|
|
4adabab256 | ||
|
|
4182cebf6a | ||
|
|
725f55fca7 | ||
|
|
c0c9a4ff91 | ||
|
|
5f1a1bb2e8 | ||
|
|
83e7809b6e | ||
|
|
46462ab2f2 | ||
|
|
afdbdfa0f1 | ||
|
|
dd9ba740ec | ||
|
|
b9614a9ee1 | ||
|
|
402471ffae | ||
|
|
91b5f1e88e | ||
|
|
12aa423f03 | ||
|
|
7c3df1bb10 | ||
|
|
f560ab029b | ||
|
|
3d69056f6a | ||
|
|
33d70ffe22 | ||
|
|
52f5640b3a | ||
|
|
eeb673787d | ||
|
|
da1fd0d8f5 | ||
|
|
a7bc65b172 | ||
|
|
42329533dc | ||
|
|
26a89dc332 | ||
|
|
86d92a03bd | ||
|
|
8c121d7352 | ||
|
|
fdf2a50fd1 | ||
|
|
268fede088 | ||
|
|
9f824266ac | ||
|
|
6971e4c028 | ||
|
|
81ea831370 | ||
|
|
b9d357b53f | ||
|
|
45f7ed96f1 | ||
|
|
e04f09edca |
@@ -0,0 +1,102 @@
|
||||
name: Clipper Github Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/clipper
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ./package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Build web package for clipper
|
||||
run: BUILD_TARGET=clipper yarn build:web
|
||||
- name: Build for Chromium (MV3)
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
run: |
|
||||
EXT_TARGET=chromium yarn build-mv3
|
||||
cd dist
|
||||
zip -r standard-notes-clipper-$PACKAGE_VERSION.chromium.zip *
|
||||
mv standard-notes-clipper-$PACKAGE_VERSION.chromium.zip ../
|
||||
- name: Build for Firefox (MV2)
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
WEB_EXT_API_KEY: ${{ secrets.AMO_WEB_EXT_API_KEY }}
|
||||
WEB_EXT_API_SECRET: ${{ secrets.AMO_WEB_EXT_API_SECRET }}
|
||||
run: |
|
||||
EXT_TARGET=firefox yarn build-mv2
|
||||
yarn web-ext sign --source-dir dist --api-key $WEB_EXT_API_KEY --api-secret $WEB_EXT_API_SECRET --channel=listed --use-submission-api
|
||||
mv web-ext-artifacts/*.xpi standard-notes-clipper-$PACKAGE_VERSION.firefox.xpi
|
||||
- name: Upload
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
name: assets
|
||||
path: |
|
||||
packages/clipper/*.chromium.zip
|
||||
packages/clipper/*.firefox.xpi
|
||||
|
||||
publish:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/clipper
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ./package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: assets
|
||||
path: packages/clipper/artifacts
|
||||
- name: Generate Release Notes
|
||||
uses: ./actions/release-notes
|
||||
with:
|
||||
package: clipper
|
||||
id: release-notes
|
||||
continue-on-error: true
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
tag_name: "@standardnotes/clipper@${{ env.PACKAGE_VERSION }}"
|
||||
name: "Clipper ${{ env.PACKAGE_VERSION }}"
|
||||
body: ${{ steps.release-notes.outputs.result }}
|
||||
draft: false
|
||||
files: packages/clipper/artifacts/*
|
||||
- name: Publish to Chrome Web Store
|
||||
uses: trmcnvn/chrome-addon@v2
|
||||
with:
|
||||
extension: heapafmadojoodklnkhjanbinemaagok
|
||||
zip: packages/clipper/artifacts/standard-notes-clipper-${{ env.PACKAGE_VERSION }}.chromium.zip
|
||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [1.25.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.25.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.25.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.25.12",
|
||||
"version": "1.26.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.46.6",
|
||||
"@standardnotes/domain-core": "^1.11.3",
|
||||
"@standardnotes/domain-core": "^1.12.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
@@ -19,6 +19,7 @@
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@standardnotes/web": "workspace:*",
|
||||
"@types/webextension-polyfill": "^0.10.0",
|
||||
"babel-loader": "^9.1.0",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.105.58](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.57](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.55](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.50](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.105.48",
|
||||
"version": "3.105.58",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.25",
|
||||
"version": "1.21.28",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.59.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [1.58.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.58.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.58.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.58.14",
|
||||
"version": "1.59.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.46.6",
|
||||
"@standardnotes/domain-core": "^1.11.3",
|
||||
"@standardnotes/domain-core": "^1.12.0",
|
||||
"@standardnotes/security": "^1.7.6",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ export function GetDeprecatedFeatures(): FeatureDescription[] {
|
||||
deprecated: true,
|
||||
permission_name: PermissionName.BoldEditor,
|
||||
description: 'A simple and peaceful rich editor that helps you write and think clearly.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/bold.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/bold.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ export function GetDeprecatedFeatures(): FeatureDescription[] {
|
||||
deprecated: true,
|
||||
permission_name: PermissionName.MarkdownBasicEditor,
|
||||
description: 'A Markdown editor with dynamic split-pane preview.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/simple-markdown.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/simple-markdown.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -81,7 +81,7 @@ export function GetDeprecatedFeatures(): FeatureDescription[] {
|
||||
spellcheckControl: true,
|
||||
deprecated: true,
|
||||
description: 'A minimal Markdown editor with live rendering and in-text search via Ctrl/Cmd + F',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/min-markdown.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/min-markdown.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ export function GetDeprecatedFeatures(): FeatureDescription[] {
|
||||
deprecated: true,
|
||||
index_path: 'index.html',
|
||||
description: 'A beautiful split-pane Markdown editor with synced-scroll, LaTeX support, and colorful syntax.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/fancy-markdown.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/fancy-markdown.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -123,7 +123,7 @@ export function GetDeprecatedFeatures(): FeatureDescription[] {
|
||||
deprecated: true,
|
||||
description:
|
||||
'Encrypted attachments for your notes using your Dropbox, Google Drive, or WebDAV server. Limited to 50MB per file.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/FileSafe-banner.png',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/FileSafe-banner.png',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
description:
|
||||
'Syntax highlighting and convenient keyboard shortcuts for over 120 programming' +
|
||||
' languages. Ideal for code snippets and procedures.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/code.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/code.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -34,7 +34,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
spellcheckControl: true,
|
||||
description:
|
||||
'From highlighting to custom font sizes and colors, to tables and lists, this editor is perfect for crafting any document.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/plus-editor.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/plus-editor.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
spellcheckControl: true,
|
||||
description:
|
||||
'A fully featured Markdown editor that supports live preview, a styling toolbar, and split pane support.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/adv-markdown.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/adv-markdown.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
permission_name: PermissionName.TaskEditor,
|
||||
description:
|
||||
'A great way to manage short-term and long-term to-do"s. You can mark tasks as completed, change their order, and edit the text naturally in place.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/task-editor.jpg',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/task-editor.jpg',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -77,7 +77,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
permission_name: PermissionName.TokenVaultEditor,
|
||||
description:
|
||||
'Encrypt and protect your 2FA secrets for all your internet accounts. Authenticator handles your 2FA secrets so that you never lose them again, or have to start over when you get a new device.',
|
||||
thumbnail_url: 'https://standard-notes.s3.amazonaws.com/screenshots/models/editors/token-vault.png',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/token-vault.png',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
@@ -91,7 +91,7 @@ export function editors(): EditorFeatureDescription[] {
|
||||
permission_name: PermissionName.SheetsEditor,
|
||||
description:
|
||||
'A powerful spreadsheet editor with formatting and formula support. Not recommended for large data sets, as encryption of such data may decrease editor performance.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/editors/spreadsheets.png',
|
||||
thumbnail_url: 'https://assets.standardnotes.com/screenshots/models/editors/spreadsheets.png',
|
||||
availableInRoles: [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser],
|
||||
})
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.30",
|
||||
"version": "1.28.33",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.40",
|
||||
"version": "1.14.43",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.52.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** Android U2F authenticator response encoding ([#2313](https://github.com/standardnotes/app/issues/2313)) ([4adabab](https://github.com/standardnotes/app/commit/4adabab256dd2efde4209d2321564206f6719c69))
|
||||
|
||||
# [3.52.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [3.51.87](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.86](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.85](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.84](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.83](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.51.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -178,6 +178,7 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
|
||||
implementation 'de.undercouch:gradle-download-task:5.0.2'
|
||||
implementation 'com.google.android.gms:play-services-fido:20.0.1'
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.standardnotes;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ActivityEventListener;
|
||||
import com.facebook.react.bridge.BaseActivityEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.google.android.gms.fido.fido2.Fido2ApiClient;
|
||||
import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse;
|
||||
import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse;
|
||||
import com.google.android.gms.fido.fido2.api.common.AuthenticationExtensionsClientOutputs;
|
||||
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential;
|
||||
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialType;
|
||||
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor;
|
||||
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRequestOptions;
|
||||
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRequestOptions;
|
||||
import com.google.android.gms.fido.Fido;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
public class Fido2ApiModule extends ReactContextBaseJavaModule {
|
||||
private final Fido2ApiClient fido2ApiClient;
|
||||
private final ReactApplicationContext reactContext;
|
||||
private static final int SIGN_REQUEST_CODE = 111;
|
||||
|
||||
private static final String LOGS_TAG = "Fido2ApiModule";
|
||||
private static final String RP_ID = "app.standardnotes.com";
|
||||
|
||||
private Promise signInPromise;
|
||||
|
||||
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
|
||||
@Override
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
|
||||
super.onActivityResult(activity, requestCode, resultCode, intent);
|
||||
|
||||
if (requestCode == SIGN_REQUEST_CODE) {
|
||||
if (signInPromise != null) {
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
Log.e(LOGS_TAG, "FIDO sign in cancelled");
|
||||
|
||||
signInPromise.reject("FIDO sign in cancelled");
|
||||
} else if (resultCode == Activity.RESULT_OK) {
|
||||
if (intent.hasExtra(Fido.FIDO2_KEY_ERROR_EXTRA)) {
|
||||
AuthenticatorErrorResponse authenticatorErrorResponse =
|
||||
AuthenticatorErrorResponse.deserializeFromBytes(intent.getByteArrayExtra(Fido.FIDO2_KEY_ERROR_EXTRA));
|
||||
Log.e(LOGS_TAG, "Fido Error: " + authenticatorErrorResponse.getErrorMessage());
|
||||
|
||||
signInPromise.reject(authenticatorErrorResponse.getErrorMessage());
|
||||
} else if (intent.hasExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)) {
|
||||
PublicKeyCredential publicKeyCredential =
|
||||
PublicKeyCredential.deserializeFromBytes(
|
||||
intent.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA));
|
||||
AuthenticatorAssertionResponse signedData =
|
||||
(AuthenticatorAssertionResponse) publicKeyCredential.getResponse();
|
||||
|
||||
WritableMap signInResult = Arguments.createMap();
|
||||
signInResult.putString("id", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
signInResult.putString("rawId", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
|
||||
byte[] extensionOutputsBytes = null;
|
||||
AuthenticationExtensionsClientOutputs extensionOutputs = publicKeyCredential.getClientExtensionResults();
|
||||
if (extensionOutputs != null) {
|
||||
extensionOutputsBytes = extensionOutputs.serializeToBytes();
|
||||
if (extensionOutputsBytes != null) {
|
||||
signInResult.putString("clientExtensionResults", Base64.encodeToString(extensionOutputsBytes, Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
}
|
||||
}
|
||||
|
||||
WritableMap response = Arguments.createMap();
|
||||
response.putString("clientDataJSON", Base64.encodeToString(signedData.getClientDataJSON(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
response.putString("authenticatorData", Base64.encodeToString(signedData.getAuthenticatorData(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
response.putString("signature", Base64.encodeToString(signedData.getSignature(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
byte[] userHandle = signedData.getUserHandle();
|
||||
if (userHandle != null) {
|
||||
response.putString("userHandle", Base64.encodeToString(userHandle, Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
|
||||
}
|
||||
signInResult.putMap("response", response);
|
||||
signInResult.putString("type", PublicKeyCredentialType.PUBLIC_KEY.toString());
|
||||
|
||||
signInPromise.resolve(signInResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
signInPromise = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Fido2ApiModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
|
||||
fido2ApiClient = Fido.getFido2ApiClient(context);
|
||||
context.addActivityEventListener(activityEventListener);
|
||||
|
||||
this.reactContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Fido2ApiModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void promptForU2FAuthentication(String authenticationOptionsJSONString, Promise promise) throws JSONException {
|
||||
signInPromise = promise;
|
||||
|
||||
JSONObject authenticationOptions = new JSONObject(authenticationOptionsJSONString);
|
||||
|
||||
ArrayList<PublicKeyCredentialDescriptor> allowedKeys = new ArrayList<PublicKeyCredentialDescriptor>();
|
||||
|
||||
JSONArray allowedCredentials = authenticationOptions.getJSONArray("allowCredentials");
|
||||
for (int i = 0, size = allowedCredentials.length(); i < size; i++) {
|
||||
JSONObject allowedCredential = allowedCredentials.getJSONObject(i);
|
||||
allowedKeys.add(
|
||||
new PublicKeyCredentialDescriptor(
|
||||
PublicKeyCredentialType.PUBLIC_KEY.toString(),
|
||||
this.convertBase64URLStringToBytes(allowedCredential.getString("id")),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
String challenge = authenticationOptions.getString("challenge");
|
||||
Double timeout = authenticationOptions.getDouble("timeout") / 1000d;
|
||||
|
||||
PublicKeyCredentialRequestOptions.Builder optionsBuilder = new PublicKeyCredentialRequestOptions
|
||||
.Builder()
|
||||
.setRpId(RP_ID)
|
||||
.setAllowList(allowedKeys)
|
||||
.setChallenge(this.convertBase64URLStringToBytes(challenge))
|
||||
.setTimeoutSeconds(timeout);
|
||||
|
||||
PublicKeyCredentialRequestOptions options = optionsBuilder.build();
|
||||
|
||||
Task result = this.fido2ApiClient.getSignPendingIntent(options);
|
||||
|
||||
final Activity activity = this.reactContext.getCurrentActivity();
|
||||
|
||||
result.addOnSuccessListener(
|
||||
new OnSuccessListener<PendingIntent>() {
|
||||
@Override
|
||||
public void onSuccess(PendingIntent fido2PendingIntent) {
|
||||
if (fido2PendingIntent == null) {
|
||||
Log.e(LOGS_TAG, "No pending FIDO intent returned");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
activity.startIntentSenderForResult(
|
||||
fido2PendingIntent.getIntentSender(),
|
||||
SIGN_REQUEST_CODE,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
} catch (IntentSender.SendIntentException exception) {
|
||||
Log.e(LOGS_TAG, "Error starting FIDO intent: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
result.addOnFailureListener(
|
||||
new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
Log.e(LOGS_TAG, "Error getting FIDO intent: " + e.getMessage());
|
||||
signInPromise.reject(e.getMessage());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] convertBase64URLStringToBytes(String base64URLString) {
|
||||
String base64String = base64URLString.replace('-', '+').replace('_', '/');
|
||||
int padding = (4 - (base64String.length() % 4)) % 4;
|
||||
for (int i = 0; i < padding; i++) {
|
||||
base64String += '=';
|
||||
}
|
||||
|
||||
return Base64.decode(base64String, Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.standardnotes;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Fido2ApiPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
modules.add(new Fido2ApiModule(reactContext));
|
||||
|
||||
return modules;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
|
||||
packages.add(new Fido2ApiPackage());
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,4 +43,7 @@ newArchEnabled=false
|
||||
hermesEnabled=true
|
||||
|
||||
# Set AsyncStorage limit
|
||||
AsyncStorage_db_size_in_MB=50
|
||||
AsyncStorage_db_size_in_MB=50
|
||||
|
||||
# The URL of the server
|
||||
host=https://app.standardnotes.com
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.51.81",
|
||||
"version": "3.52.4",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
AppStateStatus,
|
||||
ColorSchemeName,
|
||||
Linking,
|
||||
NativeModules,
|
||||
PermissionsAndroid,
|
||||
Platform,
|
||||
StatusBar,
|
||||
@@ -71,6 +72,26 @@ export class MobileDevice implements MobileDeviceInterface {
|
||||
private colorSchemeService?: ColorSchemeObserverService,
|
||||
) {}
|
||||
|
||||
async authenticateWithU2F(authenticationOptionsJSONString: string): Promise<Record<string, unknown> | null> {
|
||||
const { Fido2ApiModule } = NativeModules
|
||||
|
||||
if (!Fido2ApiModule) {
|
||||
this.consoleLog('Fido2ApiModule is not available')
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await Fido2ApiModule.promptForU2FAuthentication(authenticationOptionsJSONString)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
this.consoleLog(`Fido2ApiModule.authenticateWithU2F error: ${(error as Error).message}`)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
purchaseSubscriptionIAP(plan: AppleIAPProductId): Promise<AppleIAPReceipt | undefined> {
|
||||
return PurchaseManager.getInstance().purchase(plan)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.43.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.43.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.43.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.43.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.43.13",
|
||||
"version": "1.43.16",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.235](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.234](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.233](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.232](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.231](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.230](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.229](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.228](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.227](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.226](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.225](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.224](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.224",
|
||||
"version": "1.4.235",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.13.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.13.15",
|
||||
"version": "1.13.18",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.58.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [1.57.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.57.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.57.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.57.13",
|
||||
"version": "1.58.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -18,7 +18,7 @@
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:^",
|
||||
"@standardnotes/common": "^1.46.4",
|
||||
"@standardnotes/domain-core": "^1.11.3",
|
||||
"@standardnotes/domain-core": "^1.12.0",
|
||||
"@standardnotes/encryption": "workspace:^",
|
||||
"@standardnotes/files": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Environment, Platform, RawKeychainValue } from '@standardnotes/models'
|
||||
|
||||
import { AppleIAPProductId } from './../Subscription/AppleIAPProductId'
|
||||
import { DeviceInterface } from './DeviceInterface'
|
||||
import { Environment, Platform, RawKeychainValue } from '@standardnotes/models'
|
||||
import { AppleIAPReceipt } from '../Subscription/AppleIAPReceipt'
|
||||
|
||||
export interface MobileDeviceInterface extends DeviceInterface {
|
||||
@@ -25,4 +26,5 @@ export interface MobileDeviceInterface extends DeviceInterface {
|
||||
getAppState(): Promise<'active' | 'background' | 'inactive' | 'unknown' | 'extension'>
|
||||
getColorScheme(): Promise<'light' | 'dark' | null | undefined>
|
||||
purchaseSubscriptionIAP(plan: AppleIAPProductId): Promise<AppleIAPReceipt | undefined>
|
||||
authenticateWithU2F(authenticationOptionsJSONString: string): Promise<Record<string, unknown> | null>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.167.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [2.166.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.166.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.166.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -102,6 +102,7 @@ import { ListRevisions } from '@Lib/Domain/UseCase/ListRevisions/ListRevisions'
|
||||
import { GetRevision } from '@Lib/Domain/UseCase/GetRevision/GetRevision'
|
||||
import { DeleteRevision } from '@Lib/Domain/UseCase/DeleteRevision/DeleteRevision'
|
||||
import { GetAuthenticatorAuthenticationResponse } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponse'
|
||||
import { GetAuthenticatorAuthenticationOptions } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
||||
|
||||
/** How often to automatically sync, in milliseconds */
|
||||
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
|
||||
@@ -182,6 +183,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
private declare _addAuthenticator: AddAuthenticator
|
||||
private declare _listAuthenticators: ListAuthenticators
|
||||
private declare _deleteAuthenticator: DeleteAuthenticator
|
||||
private declare _getAuthenticatorAuthenticationOptions: GetAuthenticatorAuthenticationOptions
|
||||
private declare _getAuthenticatorAuthenticationResponse: GetAuthenticatorAuthenticationResponse
|
||||
private declare _listRevisions: ListRevisions
|
||||
private declare _getRevision: GetRevision
|
||||
@@ -284,6 +286,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this._deleteAuthenticator
|
||||
}
|
||||
|
||||
get getAuthenticatorAuthenticationOptions(): GetAuthenticatorAuthenticationOptions {
|
||||
return this._getAuthenticatorAuthenticationOptions
|
||||
}
|
||||
|
||||
get getAuthenticatorAuthenticationResponse(): GetAuthenticatorAuthenticationResponse {
|
||||
return this._getAuthenticatorAuthenticationResponse
|
||||
}
|
||||
@@ -1819,8 +1825,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
|
||||
this._deleteAuthenticator = new DeleteAuthenticator(this.authenticatorManager)
|
||||
|
||||
this._getAuthenticatorAuthenticationOptions = new GetAuthenticatorAuthenticationOptions(this.authenticatorManager)
|
||||
|
||||
this._getAuthenticatorAuthenticationResponse = new GetAuthenticatorAuthenticationResponse(
|
||||
this.authenticatorManager,
|
||||
this._getAuthenticatorAuthenticationOptions,
|
||||
this.options.u2fAuthenticatorVerificationPromptFunction,
|
||||
)
|
||||
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { AuthenticatorClientInterface } from '@standardnotes/services'
|
||||
|
||||
import { GetAuthenticatorAuthenticationOptions } from './GetAuthenticatorAuthenticationOptions'
|
||||
|
||||
describe('GetAuthenticatorAuthenticationOptions', () => {
|
||||
let authenticatorClient: AuthenticatorClientInterface
|
||||
|
||||
const createUseCase = () => new GetAuthenticatorAuthenticationOptions(authenticatorClient)
|
||||
|
||||
beforeEach(() => {
|
||||
authenticatorClient = {} as jest.Mocked<AuthenticatorClientInterface>
|
||||
authenticatorClient.generateAuthenticationOptions = jest.fn().mockResolvedValue({ foo: 'bar' })
|
||||
})
|
||||
|
||||
it('should return an error if username is not provided', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
username: '',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator authentication options: Username cannot be empty')
|
||||
})
|
||||
|
||||
it('should return an error if authenticator client fails to generate authentication options', async () => {
|
||||
authenticatorClient.generateAuthenticationOptions = jest.fn().mockResolvedValue(null)
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
username: '[email protected]',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator authentication options')
|
||||
})
|
||||
|
||||
it('should return ok if authenticator client succeeds to generate authenticator response', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
username: '[email protected]',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
})
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { AuthenticatorClientInterface } from '@standardnotes/services'
|
||||
import { Result, UseCaseInterface, Username } from '@standardnotes/domain-core'
|
||||
|
||||
import { GetAuthenticatorAuthenticationOptionsDTO } from './GetAuthenticatorAuthenticationOptionsDTO'
|
||||
|
||||
export class GetAuthenticatorAuthenticationOptions implements UseCaseInterface<Record<string, unknown>> {
|
||||
constructor(private authenticatorClient: AuthenticatorClientInterface) {}
|
||||
|
||||
async execute(dto: GetAuthenticatorAuthenticationOptionsDTO): Promise<Result<Record<string, unknown>>> {
|
||||
const usernameOrError = Username.create(dto.username)
|
||||
if (usernameOrError.isFailed()) {
|
||||
return Result.fail(`Could not generate authenticator authentication options: ${usernameOrError.getError()}`)
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
const authenticationOptions = await this.authenticatorClient.generateAuthenticationOptions(username)
|
||||
if (authenticationOptions === null) {
|
||||
return Result.fail('Could not generate authenticator authentication options')
|
||||
}
|
||||
|
||||
return Result.ok(authenticationOptions)
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export interface GetAuthenticatorAuthenticationOptionsDTO {
|
||||
username: string
|
||||
}
|
||||
+10
-19
@@ -1,40 +1,31 @@
|
||||
import { AuthenticatorClientInterface } from '@standardnotes/services'
|
||||
|
||||
import { GetAuthenticatorAuthenticationResponse } from './GetAuthenticatorAuthenticationResponse'
|
||||
import { GetAuthenticatorAuthenticationOptions } from '../GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
describe('GetAuthenticatorAuthenticationResponse', () => {
|
||||
let authenticatorClient: AuthenticatorClientInterface
|
||||
let getAuthenticatorAuthenticationOptions: GetAuthenticatorAuthenticationOptions
|
||||
let authenticatorVerificationPromptFunction: (
|
||||
authenticationOptions: Record<string, unknown>,
|
||||
) => Promise<Record<string, unknown>>
|
||||
|
||||
const createUseCase = () => new GetAuthenticatorAuthenticationResponse(authenticatorClient, authenticatorVerificationPromptFunction)
|
||||
const createUseCase = () => new GetAuthenticatorAuthenticationResponse(getAuthenticatorAuthenticationOptions, authenticatorVerificationPromptFunction)
|
||||
|
||||
beforeEach(() => {
|
||||
authenticatorClient = {} as jest.Mocked<AuthenticatorClientInterface>
|
||||
authenticatorClient.generateAuthenticationOptions = jest.fn().mockResolvedValue({ foo: 'bar' })
|
||||
getAuthenticatorAuthenticationOptions = {} as jest.Mocked<GetAuthenticatorAuthenticationOptions>
|
||||
getAuthenticatorAuthenticationOptions.execute = jest.fn().mockResolvedValue(Result.ok({ foo: 'bar' }))
|
||||
|
||||
authenticatorVerificationPromptFunction = jest.fn()
|
||||
})
|
||||
|
||||
it('should return an error if username is not provided', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
username: '',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator authentication options: Username cannot be empty')
|
||||
})
|
||||
|
||||
it('should return an error if authenticator client fails to generate authentication options', async () => {
|
||||
authenticatorClient.generateAuthenticationOptions = jest.fn().mockResolvedValue(null)
|
||||
it('should return an error if it fails to generate authentication options', async () => {
|
||||
getAuthenticatorAuthenticationOptions.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
username: '[email protected]',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator authentication options')
|
||||
expect(result.getError()).toBe('error')
|
||||
})
|
||||
|
||||
it('should return an error if authenticator verification prompt function fails', async () => {
|
||||
@@ -57,7 +48,7 @@ describe('GetAuthenticatorAuthenticationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if authenticatorVerificationPromptFunction is not provided', async () => {
|
||||
const result = await new GetAuthenticatorAuthenticationResponse(authenticatorClient).execute({
|
||||
const result = await new GetAuthenticatorAuthenticationResponse(getAuthenticatorAuthenticationOptions).execute({
|
||||
username: '[email protected]',
|
||||
})
|
||||
|
||||
|
||||
+9
-12
@@ -1,10 +1,10 @@
|
||||
import { AuthenticatorClientInterface } from '@standardnotes/services'
|
||||
import { Result, UseCaseInterface, Username } from '@standardnotes/domain-core'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { GetAuthenticatorAuthenticationResponseDTO } from './GetAuthenticatorAuthenticationResponseDTO'
|
||||
import { GetAuthenticatorAuthenticationOptions } from '../GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
||||
|
||||
export class GetAuthenticatorAuthenticationResponse implements UseCaseInterface<Record<string, unknown>> {
|
||||
constructor(
|
||||
private authenticatorClient: AuthenticatorClientInterface,
|
||||
private getAuthenticatorAuthenticationOptions: GetAuthenticatorAuthenticationOptions,
|
||||
private authenticatorVerificationPromptFunction?: (
|
||||
authenticationOptions: Record<string, unknown>,
|
||||
) => Promise<Record<string, unknown>>,
|
||||
@@ -17,16 +17,13 @@ export class GetAuthenticatorAuthenticationResponse implements UseCaseInterface<
|
||||
)
|
||||
}
|
||||
|
||||
const usernameOrError = Username.create(dto.username)
|
||||
if (usernameOrError.isFailed()) {
|
||||
return Result.fail(`Could not generate authenticator authentication options: ${usernameOrError.getError()}`)
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
const authenticationOptions = await this.authenticatorClient.generateAuthenticationOptions(username)
|
||||
if (authenticationOptions === null) {
|
||||
return Result.fail('Could not generate authenticator authentication options')
|
||||
const authenticationOptionsOrError = await this.getAuthenticatorAuthenticationOptions.execute({
|
||||
username: dto.username,
|
||||
})
|
||||
if (authenticationOptionsOrError.isFailed()) {
|
||||
return Result.fail(authenticationOptionsOrError.getError())
|
||||
}
|
||||
const authenticationOptions = authenticationOptionsOrError.getValue()
|
||||
|
||||
let authenticatorResponse
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { GetAuthenticatorAuthenticationResponse } from './GetAuthenticatorAuthen
|
||||
import { ListRevisions } from './ListRevisions/ListRevisions'
|
||||
import { GetRevision } from './GetRevision/GetRevision'
|
||||
import { DeleteRevision } from './DeleteRevision/DeleteRevision'
|
||||
import { GetAuthenticatorAuthenticationOptions } from './GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
||||
|
||||
export interface UseCaseContainerInterface {
|
||||
get signInWithRecoveryCodes(): SignInWithRecoveryCodes
|
||||
@@ -15,6 +16,7 @@ export interface UseCaseContainerInterface {
|
||||
get listAuthenticators(): ListAuthenticators
|
||||
get deleteAuthenticator(): DeleteAuthenticator
|
||||
get getAuthenticatorAuthenticationResponse(): GetAuthenticatorAuthenticationResponse
|
||||
get getAuthenticatorAuthenticationOptions(): GetAuthenticatorAuthenticationOptions
|
||||
get listRevisions(): ListRevisions
|
||||
get getRevision(): GetRevision
|
||||
get deleteRevision(): DeleteRevision
|
||||
|
||||
@@ -6,6 +6,8 @@ export * from './UseCase/DeleteAuthenticator/DeleteAuthenticator'
|
||||
export * from './UseCase/DeleteAuthenticator/DeleteAuthenticatorDTO'
|
||||
export * from './UseCase/DeleteRevision/DeleteRevision'
|
||||
export * from './UseCase/DeleteRevision/DeleteRevisionDTO'
|
||||
export * from './UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
||||
export * from './UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptionsDTO'
|
||||
export * from './UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponse'
|
||||
export * from './UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponseDTO'
|
||||
export * from './UseCase/GetRecoveryCodes/GetRecoveryCodes'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.166.20",
|
||||
"version": "2.167.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"@babel/preset-env": "*",
|
||||
"@standardnotes/api": "workspace:*",
|
||||
"@standardnotes/common": "^1.46.6",
|
||||
"@standardnotes/domain-core": "^1.11.3",
|
||||
"@standardnotes/domain-core": "^1.12.0",
|
||||
"@standardnotes/domain-events": "^2.108.1",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [1.24.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.24.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.24.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.24.43",
|
||||
"version": "1.25.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { WebApplicationInterface } from '@standardnotes/snjs/dist/@types'
|
||||
import { WebApplicationInterface } from '@standardnotes/snjs'
|
||||
import { jsonTestData, htmlTestData } from './testData'
|
||||
import { GoogleKeepConverter } from './GoogleKeepConverter'
|
||||
|
||||
|
||||
@@ -3,6 +3,52 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.151.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.151.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where the Super note markdown preview modal could not be closed on mobile ([fc137a7](https://github.com/standardnotes/app/commit/fc137a7e7ba5bd9329b97c8197f9cf65a0bbad17))
|
||||
|
||||
## [3.151.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.151.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))
|
||||
|
||||
## [3.150.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.150.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.150.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.150.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where a new note would not be correctly synced after making changes ([9f82426](https://github.com/standardnotes/app/commit/9f824266ac47280c982da3900c107cc8bb239233))
|
||||
|
||||
## [3.150.50](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.150.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.150.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
@@ -1,5 +1,124 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.151.3",
|
||||
"title": "[3.151.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.151.2",
|
||||
"title": "[3.151.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where the Super note markdown preview modal could not be closed on mobile ([fc137a7](https://github.com/standardnotes/app/commit/fc137a7e7ba5bd9329b97c8197f9cf65a0bbad17))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where the Super note markdown preview modal could not be closed on mobile (fc137a7)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where the Super note markdown preview modal could not be closed on mobile (fc137a7)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.151.1",
|
||||
"title": "[3.151.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.151.0",
|
||||
"title": "[3.151.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* **mobile:** add U2F support for Android devices ([#2311](https://github.com/standardnotes/app/issues/2311)) ([725f55f](https://github.com/standardnotes/app/commit/725f55fca794b86c245742837af7e4370cebd68c))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"mobile: add U2F support for Android devices (#2311) (725f55f)"
|
||||
],
|
||||
"Features": [
|
||||
"mobile: add U2F support for Android devices (#2311) (725f55f)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.54",
|
||||
"title": "[3.150.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-15)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.53",
|
||||
"title": "[3.150.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-14)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.52",
|
||||
"title": "[3.150.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.51",
|
||||
"title": "[3.150.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where a new note would not be correctly synced after making changes ([9f82426](https://github.com/standardnotes/app/commit/9f824266ac47280c982da3900c107cc8bb239233))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where a new note would not be correctly synced after making changes (9f82426)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where a new note would not be correctly synced after making changes (9f82426)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.50",
|
||||
"title": "[3.150.50](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.49",
|
||||
"title": "[3.150.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-12)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.150.48",
|
||||
"title": "[3.150.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-11)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.150.48",
|
||||
"version": "3.151.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@babel/plugin-transform-react-jsx": "^7.19.0",
|
||||
"@babel/preset-env": "*",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@lexical/react": "0.9.1",
|
||||
"@lexical/react": "0.9.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@reach/alert": "^0.18.0",
|
||||
"@reach/alert-dialog": "^0.18.0",
|
||||
@@ -84,7 +84,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"lexical": "0.9.1",
|
||||
"lexical": "0.9.2",
|
||||
"lint-staged": ">=13",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"minimatch": "^5.1.1",
|
||||
@@ -117,7 +117,7 @@
|
||||
"app/**/*.{js,ts,jsx,tsx,css,md}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lexical/headless": "0.9.1",
|
||||
"@lexical/headless": "0.9.2",
|
||||
"contactjs": "2.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.standardnotes.dev",
|
||||
"package_name": "com.standardnotes",
|
||||
"sha256_cert_fingerprints": [
|
||||
"CC:33:62:8B:53:9A:1E:1E:99:F2:75:7C:FC:7C:01:5D:6D:5A:99:D9:DD:93:6A:75:42:4B:08:BD:93:43:10:99"
|
||||
"58:3F:C4:1B:49:0C:38:0B:56:FB:E9:EE:47:38:1E:B4:EF:53:E0:37:19:A5:50:3F:C1:F2:9D:CD:8E:28:90:2A"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,12 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuItem onClick={() => viewControllerManager.isImportModalVisible.set(true)}>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
viewControllerManager.importModalController.setIsVisible(true)
|
||||
viewControllerManager.accountMenuController.closeAccountMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="archive" className={iconClassName} />
|
||||
Import
|
||||
</MenuItem>
|
||||
|
||||
@@ -272,7 +272,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
application={application}
|
||||
viewControllerManager={viewControllerManager}
|
||||
/>
|
||||
<ImportModal viewControllerManager={viewControllerManager} />
|
||||
<ImportModal importModalController={viewControllerManager.importModalController} />
|
||||
</>
|
||||
{application.routeService.isDotOrg && <DotOrgNotice />}
|
||||
{isIOS() && <IosKeyboardClose />}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Username } from '@standardnotes/snjs'
|
||||
import { ChallengePrompt } from '@standardnotes/services'
|
||||
import { RefObject, useState } from 'react'
|
||||
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { isAndroid } from '@/Utils'
|
||||
|
||||
import Button from '../Button/Button'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
import { InputValue } from './InputValue'
|
||||
import U2FPromptIframeContainer from './U2FPromptIframeContainer'
|
||||
|
||||
@@ -18,7 +23,7 @@ const U2FPrompt = ({ application, onValueChange, prompt, buttonRef, contextData
|
||||
const [authenticatorResponse, setAuthenticatorResponse] = useState<Record<string, unknown> | null>(null)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
if (!application.isFullU2FClient) {
|
||||
if (!application.isFullU2FClient && !isAndroid()) {
|
||||
return (
|
||||
<U2FPromptIframeContainer
|
||||
contextData={contextData}
|
||||
@@ -28,48 +33,71 @@ const U2FPrompt = ({ application, onValueChange, prompt, buttonRef, contextData
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="min-w-76">
|
||||
{error && <div className="text-red-500">{error}</div>}
|
||||
<Button
|
||||
primary
|
||||
fullWidth
|
||||
colorStyle={authenticatorResponse ? 'success' : 'info'}
|
||||
onClick={async () => {
|
||||
const usernameOrError = Username.create((contextData as { username: string }).username)
|
||||
if (usernameOrError.isFailed()) {
|
||||
setError(usernameOrError.getError())
|
||||
return
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
let authenticatorResponse: Record<string, unknown> | null = null
|
||||
if (isAndroid()) {
|
||||
const authenticatorOptionsOrError = await application.getAuthenticatorAuthenticationOptions.execute({
|
||||
username: username.value,
|
||||
})
|
||||
if (authenticatorOptionsOrError.isFailed()) {
|
||||
setError(authenticatorOptionsOrError.getError())
|
||||
return
|
||||
}
|
||||
const authenticatorOptions = authenticatorOptionsOrError.getValue()
|
||||
|
||||
authenticatorResponse = await application
|
||||
.mobileDevice()
|
||||
.authenticateWithU2F(JSON.stringify(authenticatorOptions))
|
||||
} else {
|
||||
const authenticatorResponseOrError = await application.getAuthenticatorAuthenticationResponse.execute({
|
||||
username: username.value,
|
||||
})
|
||||
|
||||
if (authenticatorResponseOrError.isFailed()) {
|
||||
setError(authenticatorResponseOrError.getError())
|
||||
return
|
||||
}
|
||||
|
||||
authenticatorResponse = authenticatorResponseOrError.getValue()
|
||||
}
|
||||
|
||||
if (authenticatorResponse === null) {
|
||||
setError('Failed to obtain device response')
|
||||
return
|
||||
}
|
||||
|
||||
setAuthenticatorResponse(authenticatorResponse)
|
||||
onValueChange(authenticatorResponse, prompt)
|
||||
}}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{authenticatorResponse ? (
|
||||
<span className="flex items-center justify-center gap-3">
|
||||
<Icon type="check-circle" />
|
||||
Obtained Device Response
|
||||
</span>
|
||||
) : (
|
||||
'Authenticate Device'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-w-76">
|
||||
{error && <div className="text-red-500">{error}</div>}
|
||||
<Button
|
||||
primary
|
||||
fullWidth
|
||||
colorStyle={authenticatorResponse ? 'success' : 'info'}
|
||||
onClick={async () => {
|
||||
if (!contextData || contextData.username === undefined) {
|
||||
setError('No username provided')
|
||||
return
|
||||
}
|
||||
|
||||
const authenticatorResponseOrError = await application.getAuthenticatorAuthenticationResponse.execute({
|
||||
username: contextData.username as string,
|
||||
})
|
||||
|
||||
if (authenticatorResponseOrError.isFailed()) {
|
||||
setError(authenticatorResponseOrError.getError())
|
||||
return
|
||||
}
|
||||
|
||||
const authenticatorResponse = authenticatorResponseOrError.getValue()
|
||||
|
||||
setAuthenticatorResponse(authenticatorResponse)
|
||||
onValueChange(authenticatorResponse, prompt)
|
||||
}}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{authenticatorResponse ? (
|
||||
<span className="flex items-center justify-center gap-3">
|
||||
<Icon type="check-circle" />
|
||||
Obtained Device Response
|
||||
</span>
|
||||
) : (
|
||||
'Authenticate Device'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default U2FPrompt
|
||||
|
||||
@@ -354,7 +354,7 @@ class Footer extends AbstractComponent<Props, State> {
|
||||
id="footer-bar"
|
||||
className="z-footer-bar hidden h-8 w-full select-none items-center justify-between border-t border-border bg-contrast px-3 text-text md:flex"
|
||||
>
|
||||
<div className="left flex h-full">
|
||||
<div className="left flex h-full flex-shrink-0">
|
||||
<div className="sk-app-bar-item relative z-footer-bar-item ml-0 select-none">
|
||||
<AccountMenuButton
|
||||
application={this.application}
|
||||
@@ -393,14 +393,14 @@ class Footer extends AbstractComponent<Props, State> {
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div className="center">
|
||||
<div className="center max-h-full overflow-hidden px-4">
|
||||
{this.state.arbitraryStatusMessage && (
|
||||
<div className="relative z-footer-bar-item flex select-none items-center text-xs font-bold text-neutral">
|
||||
<div className="relative z-footer-bar-item max-h-full select-none items-center overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold text-neutral">
|
||||
{this.state.arbitraryStatusMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="right flex h-full">
|
||||
<div className="right flex h-full flex-shrink-0">
|
||||
{this.state.dataUpgradeAvailable && (
|
||||
<div
|
||||
onClick={this.securityUpdateClickHandler}
|
||||
@@ -418,7 +418,7 @@ class Footer extends AbstractComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
{(this.state.outOfSync || this.state.showSyncResolution) && (
|
||||
<div className="relative z-footer-bar-item ml-3 flex select-none items-center">
|
||||
<div className="relative z-footer-bar-item ml-3 flex flex-shrink-0 select-none items-center">
|
||||
{this.state.outOfSync && (
|
||||
<div onClick={this.syncResolutionClickHandler} className="text-xs font-bold text-warning">
|
||||
Potentially Out of Sync
|
||||
@@ -430,7 +430,7 @@ class Footer extends AbstractComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
{this.state.offline && (
|
||||
<div className="relative z-footer-bar-item ml-3 flex select-none items-center text-xs font-bold">
|
||||
<div className="relative z-footer-bar-item ml-3 flex flex-shrink-0 select-none items-center text-xs font-bold">
|
||||
Offline
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,191 +1,13 @@
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { ContentType, DecryptedTransferPayload, pluralize, SNTag, TagContent, UuidGenerator } from '@standardnotes/snjs'
|
||||
import { Importer } from '@standardnotes/ui-services'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCallback, useMemo, useReducer, useState } from 'react'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { useStateRef } from '@/Hooks/useStateRef'
|
||||
import { ImportModalFileItem } from './ImportModalFileItem'
|
||||
import { useMemo } from 'react'
|
||||
import ImportModalFileItem from './ImportModalFileItem'
|
||||
import ImportModalInitialPage from './InitialPage'
|
||||
import { ImportModalAction, ImportModalFile, ImportModalState } from './Types'
|
||||
import Modal, { ModalAction } from '../Modal/Modal'
|
||||
import ModalOverlay from '../Modal/ModalOverlay'
|
||||
import { ImportModalController } from '@/Controllers/ImportModalController'
|
||||
|
||||
const reducer = (state: ImportModalState, action: ImportModalAction): ImportModalState => {
|
||||
switch (action.type) {
|
||||
case 'setFiles':
|
||||
return {
|
||||
...state,
|
||||
files: action.files.map((file) => ({
|
||||
id: UuidGenerator.GenerateUuid(),
|
||||
file,
|
||||
status: action.service ? 'ready' : 'pending',
|
||||
service: action.service,
|
||||
})),
|
||||
}
|
||||
case 'updateFile':
|
||||
return {
|
||||
...state,
|
||||
files: state.files.map((file) => {
|
||||
if (file.file.name === action.file.file.name) {
|
||||
return action.file
|
||||
}
|
||||
return file
|
||||
}),
|
||||
}
|
||||
case 'removeFile':
|
||||
return {
|
||||
...state,
|
||||
files: state.files.filter((file) => file.id !== action.id),
|
||||
}
|
||||
case 'setImportTag':
|
||||
return {
|
||||
...state,
|
||||
importTag: action.tag,
|
||||
}
|
||||
case 'clearState':
|
||||
return {
|
||||
files: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialState: ImportModalState = {
|
||||
files: [],
|
||||
}
|
||||
|
||||
const ImportModal = ({ viewControllerManager }: { viewControllerManager: ViewControllerManager }) => {
|
||||
const application = useApplication()
|
||||
const [importer] = useState(() => new Importer(application))
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
const { files } = state
|
||||
const filesRef = useStateRef(files)
|
||||
|
||||
const importFromPayloads = useCallback(
|
||||
async (file: ImportModalFile, payloads: DecryptedTransferPayload[]) => {
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
status: 'importing',
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
await importer.importFromTransferPayloads(payloads)
|
||||
|
||||
const notesImported = payloads.filter((payload) => payload.content_type === ContentType.Note)
|
||||
const tagsImported = payloads.filter((payload) => payload.content_type === ContentType.Tag)
|
||||
|
||||
const successMessage =
|
||||
`Successfully imported ${notesImported.length} ` +
|
||||
pluralize(notesImported.length, 'note', 'notes') +
|
||||
(tagsImported.length > 0
|
||||
? ` and ${tagsImported.length} ${pluralize(tagsImported.length, 'tag', 'tags')}`
|
||||
: '')
|
||||
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
status: 'success',
|
||||
successMessage,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error : new Error('Could not import file'),
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
[importer],
|
||||
)
|
||||
|
||||
const parseAndImport = useCallback(async () => {
|
||||
const files = filesRef.current
|
||||
if (files.length === 0) {
|
||||
return
|
||||
}
|
||||
const importedPayloads: DecryptedTransferPayload[] = []
|
||||
for (const file of files) {
|
||||
if (!file.service) {
|
||||
return
|
||||
}
|
||||
|
||||
if (file.status === 'ready' && file.payloads) {
|
||||
await importFromPayloads(file, file.payloads)
|
||||
importedPayloads.push(...file.payloads)
|
||||
continue
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
status: 'parsing',
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const payloads = await importer.getPayloadsFromFile(file.file, file.service)
|
||||
await importFromPayloads(file, payloads)
|
||||
importedPayloads.push(...payloads)
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error : new Error('Could not import file'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
const currentDate = new Date()
|
||||
const importTagPayload: DecryptedTransferPayload<TagContent> = {
|
||||
uuid: UuidGenerator.GenerateUuid(),
|
||||
created_at: currentDate,
|
||||
created_at_timestamp: currentDate.getTime(),
|
||||
updated_at: currentDate,
|
||||
updated_at_timestamp: currentDate.getTime(),
|
||||
content_type: ContentType.Tag,
|
||||
content: {
|
||||
title: `Imported on ${currentDate.toLocaleString()}`,
|
||||
expanded: false,
|
||||
iconString: '',
|
||||
references: importedPayloads
|
||||
.filter((payload) => payload.content_type === ContentType.Note)
|
||||
.map((payload) => ({
|
||||
content_type: ContentType.Note,
|
||||
uuid: payload.uuid,
|
||||
})),
|
||||
},
|
||||
}
|
||||
const [importTag] = await importer.importFromTransferPayloads([importTagPayload])
|
||||
if (importTag) {
|
||||
dispatch({
|
||||
type: 'setImportTag',
|
||||
tag: importTag as SNTag,
|
||||
})
|
||||
}
|
||||
}, [filesRef, importFromPayloads, importer])
|
||||
|
||||
const closeDialog = useCallback(() => {
|
||||
viewControllerManager.isImportModalVisible.set(false)
|
||||
if (state.importTag) {
|
||||
void viewControllerManager.navigationController.setSelectedTag(state.importTag, 'all', {
|
||||
userTriggered: true,
|
||||
})
|
||||
}
|
||||
dispatch({
|
||||
type: 'clearState',
|
||||
})
|
||||
}, [state.importTag, viewControllerManager.isImportModalVisible, viewControllerManager.navigationController])
|
||||
const ImportModal = ({ importModalController }: { importModalController: ImportModalController }) => {
|
||||
const { files, setFiles, updateFile, removeFile, importer, parseAndImport, isVisible, close } = importModalController
|
||||
|
||||
const isReadyToImport = files.length > 0 && files.every((file) => file.status === 'ready')
|
||||
const importSuccessOrError =
|
||||
@@ -203,22 +25,28 @@ const ImportModal = ({ viewControllerManager }: { viewControllerManager: ViewCon
|
||||
{
|
||||
label: importSuccessOrError ? 'Close' : 'Cancel',
|
||||
type: 'cancel',
|
||||
onClick: closeDialog,
|
||||
onClick: close,
|
||||
mobileSlot: 'left',
|
||||
},
|
||||
],
|
||||
[closeDialog, importSuccessOrError, isReadyToImport, parseAndImport],
|
||||
[close, importSuccessOrError, isReadyToImport, parseAndImport],
|
||||
)
|
||||
|
||||
return (
|
||||
<ModalOverlay isOpen={viewControllerManager.isImportModalVisible.get()} onDismiss={closeDialog}>
|
||||
<Modal title="Import" close={closeDialog} actions={modalActions}>
|
||||
<ModalOverlay isOpen={isVisible} onDismiss={close}>
|
||||
<Modal title="Import" close={close} actions={modalActions}>
|
||||
<div className="px-4 py-4">
|
||||
{!files.length && <ImportModalInitialPage dispatch={dispatch} />}
|
||||
{!files.length && <ImportModalInitialPage setFiles={setFiles} />}
|
||||
{files.length > 0 && (
|
||||
<div className="divide-y divide-border">
|
||||
{files.map((file) => (
|
||||
<ImportModalFileItem file={file} key={file.id} dispatch={dispatch} importer={importer} />
|
||||
<ImportModalFileItem
|
||||
file={file}
|
||||
key={file.id}
|
||||
updateFile={updateFile}
|
||||
removeFile={removeFile}
|
||||
importer={importer}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ImportModalController, ImportModalFile } from '@/Controllers/ImportModalController'
|
||||
import { classNames, ContentType, DecryptedTransferPayload, pluralize } from '@standardnotes/snjs'
|
||||
import { Importer, NoteImportType } from '@standardnotes/ui-services'
|
||||
import { Dispatch, useCallback, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { ImportModalAction, ImportModalFile } from './Types'
|
||||
|
||||
const NoteImportTypeColors: Record<NoteImportType, string> = {
|
||||
evernote: 'bg-[#14cc45] text-[#000]',
|
||||
@@ -20,13 +21,15 @@ const NoteImportTypeIcons: Record<NoteImportType, string> = {
|
||||
plaintext: 'plain-text',
|
||||
}
|
||||
|
||||
export const ImportModalFileItem = ({
|
||||
const ImportModalFileItem = ({
|
||||
file,
|
||||
dispatch,
|
||||
updateFile,
|
||||
removeFile,
|
||||
importer,
|
||||
}: {
|
||||
file: ImportModalFile
|
||||
dispatch: Dispatch<ImportModalAction>
|
||||
updateFile: ImportModalController['updateFile']
|
||||
removeFile: ImportModalController['removeFile']
|
||||
importer: Importer
|
||||
}) => {
|
||||
const setFileService = useCallback(
|
||||
@@ -34,21 +37,18 @@ export const ImportModalFileItem = ({
|
||||
let payloads: DecryptedTransferPayload[] | undefined
|
||||
try {
|
||||
payloads = service ? await importer.getPayloadsFromFile(file.file, service) : undefined
|
||||
} catch {
|
||||
//
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'updateFile',
|
||||
file: {
|
||||
...file,
|
||||
service,
|
||||
status: service ? 'ready' : 'pending',
|
||||
payloads,
|
||||
},
|
||||
updateFile({
|
||||
...file,
|
||||
service,
|
||||
status: service ? 'ready' : 'pending',
|
||||
payloads,
|
||||
})
|
||||
},
|
||||
[dispatch, file, importer],
|
||||
[file, importer, updateFile],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -59,7 +59,7 @@ export const ImportModalFileItem = ({
|
||||
if (file.service === undefined) {
|
||||
void detect()
|
||||
}
|
||||
}, [dispatch, file, setFileService])
|
||||
}, [file, setFileService])
|
||||
|
||||
const notePayloads =
|
||||
file.status === 'ready' && file.payloads
|
||||
@@ -129,10 +129,7 @@ export const ImportModalFileItem = ({
|
||||
<button
|
||||
className="ml-2 rounded border border-border bg-default p-1.5 hover:bg-contrast"
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'removeFile',
|
||||
id: file.id,
|
||||
})
|
||||
removeFile(file.id)
|
||||
}}
|
||||
>
|
||||
<Icon type="close" size="medium" />
|
||||
@@ -144,3 +141,5 @@ export const ImportModalFileItem = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(ImportModalFileItem)
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import { ImportModalController } from '@/Controllers/ImportModalController'
|
||||
import { ClassicFileReader } from '@standardnotes/filepicker'
|
||||
import { NoteImportType } from '@standardnotes/ui-services'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCallback } from 'react'
|
||||
import Button from '../Button/Button'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { ImportModalAction } from './Types'
|
||||
|
||||
type Props = {
|
||||
dispatch: React.Dispatch<ImportModalAction>
|
||||
setFiles: ImportModalController['setFiles']
|
||||
}
|
||||
|
||||
const ImportModalInitialPage = ({ dispatch }: Props) => {
|
||||
const ImportModalInitialPage = ({ setFiles }: Props) => {
|
||||
const selectFiles = useCallback(
|
||||
async (service?: NoteImportType) => {
|
||||
const files = await ClassicFileReader.selectFiles()
|
||||
|
||||
dispatch({
|
||||
type: 'setFiles',
|
||||
files,
|
||||
service,
|
||||
})
|
||||
setFiles(files, service)
|
||||
},
|
||||
[dispatch],
|
||||
[setFiles],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -33,10 +30,7 @@ const ImportModalInitialPage = ({ dispatch }: Props) => {
|
||||
onDrop={(e) => {
|
||||
e.preventDefault()
|
||||
const files = Array.from(e.dataTransfer.files)
|
||||
dispatch({
|
||||
type: 'setFiles',
|
||||
files,
|
||||
})
|
||||
setFiles(files)
|
||||
}}
|
||||
>
|
||||
<div className="text-lg font-semibold">Drag and drop files to auto-detect and import</div>
|
||||
@@ -90,4 +84,4 @@ const ImportModalInitialPage = ({ dispatch }: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportModalInitialPage
|
||||
export default observer(ImportModalInitialPage)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { DecryptedTransferPayload, SNTag } from '@standardnotes/models'
|
||||
import { NoteImportType } from '@standardnotes/ui-services'
|
||||
|
||||
type ImportModalFileCommon = {
|
||||
id: string
|
||||
file: File
|
||||
service: NoteImportType | null | undefined
|
||||
}
|
||||
|
||||
export type ImportModalFile = (
|
||||
| { status: 'pending' }
|
||||
| { status: 'ready'; payloads?: DecryptedTransferPayload[] }
|
||||
| { status: 'parsing' }
|
||||
| { status: 'importing' }
|
||||
| { status: 'success'; successMessage: string }
|
||||
| { status: 'error'; error: Error }
|
||||
) &
|
||||
ImportModalFileCommon
|
||||
|
||||
export type ImportModalState = {
|
||||
files: ImportModalFile[]
|
||||
importTag?: SNTag
|
||||
}
|
||||
|
||||
export type ImportModalAction =
|
||||
| { type: 'setFiles'; files: File[]; service?: NoteImportType }
|
||||
| { type: 'updateFile'; file: ImportModalFile }
|
||||
| { type: 'removeFile'; id: ImportModalFile['id'] }
|
||||
| { type: 'setImportTag'; tag: SNTag }
|
||||
| { type: 'clearState' }
|
||||
@@ -26,7 +26,7 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
private defaultTagUuid: UuidString | undefined
|
||||
private defaultTag?: SNTag
|
||||
|
||||
private syncController: NoteSyncController
|
||||
private syncController!: NoteSyncController
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
@@ -106,6 +106,7 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
this.isTemplateNote = true
|
||||
this.item = note
|
||||
this.syncController.setItem(this.item)
|
||||
|
||||
if (this.defaultTagUuid) {
|
||||
const tag = this.application.items.findItem(this.defaultTagUuid) as SNTag
|
||||
|
||||
@@ -6,6 +6,7 @@ import { copyTextToClipboard } from '../../Utils/copyTextToClipboard'
|
||||
import Modal, { ModalAction } from '@/Components/Modal/Modal'
|
||||
import { BlocksEditor } from './BlocksEditor'
|
||||
import { BlocksEditorComposer } from './BlocksEditorComposer'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
|
||||
type Props = {
|
||||
note: SNNote
|
||||
@@ -28,6 +29,8 @@ export const SuperNoteMarkdownPreview: FunctionComponent<Props> = ({ note, close
|
||||
setMarkdown(markdown)
|
||||
}, [])
|
||||
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
|
||||
const modalActions: ModalAction[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -36,8 +39,15 @@ export const SuperNoteMarkdownPreview: FunctionComponent<Props> = ({ note, close
|
||||
onClick: copy,
|
||||
mobileSlot: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Done',
|
||||
type: 'cancel',
|
||||
onClick: closeDialog,
|
||||
mobileSlot: 'right',
|
||||
hidden: !isMobileScreen,
|
||||
},
|
||||
],
|
||||
[copy, didCopy],
|
||||
[closeDialog, copy, didCopy, isMobileScreen],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { DecryptedTransferPayload, SNTag, TagContent } from '@standardnotes/models'
|
||||
import { ContentType, pluralize, UuidGenerator } from '@standardnotes/snjs'
|
||||
import { Importer, NoteImportType } from '@standardnotes/ui-services'
|
||||
import { action, makeObservable, observable } from 'mobx'
|
||||
import { NavigationController } from './Navigation/NavigationController'
|
||||
|
||||
type ImportModalFileCommon = {
|
||||
id: string
|
||||
file: File
|
||||
service: NoteImportType | null | undefined
|
||||
}
|
||||
|
||||
export type ImportModalFile = (
|
||||
| { status: 'pending' }
|
||||
| { status: 'ready'; payloads?: DecryptedTransferPayload[] }
|
||||
| { status: 'parsing' }
|
||||
| { status: 'importing' }
|
||||
| { status: 'success'; successMessage: string }
|
||||
| { status: 'error'; error: Error }
|
||||
) &
|
||||
ImportModalFileCommon
|
||||
|
||||
export class ImportModalController {
|
||||
isVisible = false
|
||||
importer: Importer
|
||||
files: ImportModalFile[] = []
|
||||
importTag: SNTag | undefined = undefined
|
||||
|
||||
constructor(application: WebApplication, private navigationController: NavigationController) {
|
||||
makeObservable(this, {
|
||||
isVisible: observable,
|
||||
setIsVisible: action,
|
||||
|
||||
files: observable,
|
||||
setFiles: action,
|
||||
updateFile: action,
|
||||
removeFile: action,
|
||||
|
||||
importTag: observable,
|
||||
setImportTag: action,
|
||||
})
|
||||
|
||||
this.importer = new Importer(application)
|
||||
}
|
||||
|
||||
setIsVisible = (isVisible: boolean) => {
|
||||
this.isVisible = isVisible
|
||||
}
|
||||
|
||||
setFiles = (files: File[], service?: NoteImportType) => {
|
||||
this.files = files.map((file) => ({
|
||||
id: UuidGenerator.GenerateUuid(),
|
||||
file,
|
||||
service,
|
||||
status: service ? 'ready' : 'pending',
|
||||
}))
|
||||
}
|
||||
|
||||
updateFile = (file: ImportModalFile) => {
|
||||
this.files = this.files.map((f) => (f.id === file.id ? file : f))
|
||||
}
|
||||
|
||||
removeFile = (id: ImportModalFile['id']) => {
|
||||
this.files = this.files.filter((f) => f.id !== id)
|
||||
}
|
||||
|
||||
setImportTag = (tag: SNTag | undefined) => {
|
||||
this.importTag = tag
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.setIsVisible(false)
|
||||
if (this.importTag) {
|
||||
this.navigationController
|
||||
.setSelectedTag(this.importTag, 'all', {
|
||||
userTriggered: true,
|
||||
})
|
||||
.catch(console.error)
|
||||
}
|
||||
this.setFiles([])
|
||||
this.setImportTag(undefined)
|
||||
}
|
||||
|
||||
importFromPayloads = async (file: ImportModalFile, payloads: DecryptedTransferPayload[]) => {
|
||||
this.updateFile({
|
||||
...file,
|
||||
status: 'importing',
|
||||
})
|
||||
|
||||
try {
|
||||
await this.importer.importFromTransferPayloads(payloads)
|
||||
|
||||
const notesImported = payloads.filter((payload) => payload.content_type === ContentType.Note)
|
||||
const tagsImported = payloads.filter((payload) => payload.content_type === ContentType.Tag)
|
||||
|
||||
const successMessage =
|
||||
`Successfully imported ${notesImported.length} ` +
|
||||
pluralize(notesImported.length, 'note', 'notes') +
|
||||
(tagsImported.length > 0 ? ` and ${tagsImported.length} ${pluralize(tagsImported.length, 'tag', 'tags')}` : '')
|
||||
|
||||
this.updateFile({
|
||||
...file,
|
||||
status: 'success',
|
||||
successMessage,
|
||||
})
|
||||
} catch (error) {
|
||||
this.updateFile({
|
||||
...file,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error : new Error('Could not import file'),
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
parseAndImport = async () => {
|
||||
if (this.files.length === 0) {
|
||||
return
|
||||
}
|
||||
const importedPayloads: DecryptedTransferPayload[] = []
|
||||
for (const file of this.files) {
|
||||
if (!file.service) {
|
||||
return
|
||||
}
|
||||
|
||||
if (file.status === 'ready' && file.payloads) {
|
||||
await this.importFromPayloads(file, file.payloads)
|
||||
importedPayloads.push(...file.payloads)
|
||||
continue
|
||||
}
|
||||
|
||||
this.updateFile({
|
||||
...file,
|
||||
status: 'parsing',
|
||||
})
|
||||
|
||||
try {
|
||||
const payloads = await this.importer.getPayloadsFromFile(file.file, file.service)
|
||||
await this.importFromPayloads(file, payloads)
|
||||
importedPayloads.push(...payloads)
|
||||
} catch (error) {
|
||||
this.updateFile({
|
||||
...file,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error : new Error('Could not import file'),
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
const currentDate = new Date()
|
||||
const importTagPayload: DecryptedTransferPayload<TagContent> = {
|
||||
uuid: UuidGenerator.GenerateUuid(),
|
||||
created_at: currentDate,
|
||||
created_at_timestamp: currentDate.getTime(),
|
||||
updated_at: currentDate,
|
||||
updated_at_timestamp: currentDate.getTime(),
|
||||
content_type: ContentType.Tag,
|
||||
content: {
|
||||
title: `Imported on ${currentDate.toLocaleString()}`,
|
||||
expanded: false,
|
||||
iconString: '',
|
||||
references: importedPayloads
|
||||
.filter((payload) => payload.content_type === ContentType.Note)
|
||||
.map((payload) => ({
|
||||
content_type: ContentType.Note,
|
||||
uuid: payload.uuid,
|
||||
})),
|
||||
},
|
||||
}
|
||||
const [importTag] = await this.importer.importFromTransferPayloads([importTagPayload])
|
||||
if (importTag) {
|
||||
this.setImportTag(importTag as SNTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@ export class NoteSyncController {
|
||||
|
||||
constructor(private application: WebApplication, private item: SNNote) {}
|
||||
|
||||
setItem(item: SNNote) {
|
||||
this.item = item
|
||||
}
|
||||
|
||||
deinit() {
|
||||
if (this.saveTimeout) {
|
||||
clearTimeout(this.saveTimeout)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user