mirror of
https://github.com/standardnotes/app
synced 2026-09-14 12:45:58 -04:00
Compare commits
161
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fa3fec9ab | ||
|
|
059ee9ea36 | ||
|
|
66d08895fe | ||
|
|
518643dc71 | ||
|
|
5a0c005e8c | ||
|
|
45cdab6f6c | ||
|
|
46245b5569 | ||
|
|
8b103307cb | ||
|
|
a70d1009a3 | ||
|
|
a5b1ff9595 | ||
|
|
d691d7a3b3 | ||
|
|
71675dc6a2 | ||
|
|
6a1b6cef1b | ||
|
|
3d8c12a789 | ||
|
|
370aaecfb8 | ||
|
|
68bfb2ff68 | ||
|
|
a66bb08c3b | ||
|
|
ccde207de8 | ||
|
|
d3262b0f01 | ||
|
|
ffbc984c58 | ||
|
|
979163bae7 | ||
|
|
c928f8a281 | ||
|
|
0e10b347b6 | ||
|
|
caf2c4a876 | ||
|
|
1d22365086 | ||
|
|
bd5ccbfab6 | ||
|
|
46b19d8caf | ||
|
|
a6a0d615c5 | ||
|
|
f3203e2e80 | ||
|
|
e0edabb4b9 | ||
|
|
e68c708628 | ||
|
|
e4f3f26937 | ||
|
|
8575b3e714 | ||
|
|
7c261af230 | ||
|
|
71e20f2a2f | ||
|
|
3855b9f767 | ||
|
|
5c29e22ea4 | ||
|
|
07cb00a815 | ||
|
|
4f2ee4b56f | ||
|
|
90e10c76f7 | ||
|
|
2175c9cdcb | ||
|
|
8c41f0ecc3 | ||
|
|
c8c166d2a0 | ||
|
|
6cacdd8c58 | ||
|
|
b5b6192b60 | ||
|
|
635e2d9150 | ||
|
|
0c23e4d36c | ||
|
|
1e8ce4bee6 | ||
|
|
29368c51b8 | ||
|
|
621bf1b810 | ||
|
|
28e43d37c0 | ||
|
|
e07fed267f | ||
|
|
1726302615 | ||
|
|
73ca12275f | ||
|
|
1ee6be4e5c | ||
|
|
42fc1d073d | ||
|
|
c32541c6e1 | ||
|
|
34d9100af6 | ||
|
|
7f2074a6ec | ||
|
|
0e95b451d6 | ||
|
|
0e886bf0f1 | ||
|
|
9b3f11d85b | ||
|
|
349fd7204e | ||
|
|
afa9aafa3e | ||
|
|
b57786eeeb | ||
|
|
f30d0898ec | ||
|
|
1729bc4292 | ||
|
|
abe2b17cb4 | ||
|
|
d51de34daa | ||
|
|
ec0f0a2b18 | ||
|
|
a417e5ba8d | ||
|
|
02eb969e07 | ||
|
|
767d354780 | ||
|
|
052e7d2f90 | ||
|
|
e3ec8503a5 | ||
|
|
cc2762a29d | ||
|
|
96e8dfdd31 | ||
|
|
60cbd7aed2 | ||
|
|
2b0c9b188c | ||
|
|
4181a4cca0 | ||
|
|
d51ab36bbf | ||
|
|
81774d74c6 | ||
|
|
148aee441b | ||
|
|
db3069eab3 | ||
|
|
c045eb1459 | ||
|
|
e19184c2da | ||
|
|
7f6c53dfcb | ||
|
|
1c9ad49aea | ||
|
|
6fc1bb5e84 | ||
|
|
ddd7c8f889 | ||
|
|
03eb1cc6b4 | ||
|
|
ac91621271 | ||
|
|
c01fa7bb99 | ||
|
|
6db6079790 | ||
|
|
2026d8b936 | ||
|
|
645602b9d3 | ||
|
|
3c91b0cb17 | ||
|
|
8145cdb8ac | ||
|
|
d50c5fefcb | ||
|
|
745e850a5c | ||
|
|
cadf03f0ef | ||
|
|
931b674e4e | ||
|
|
a6efc5336b | ||
|
|
21611d9b45 | ||
|
|
9b91875d3d | ||
|
|
dcc8cfbe45 | ||
|
|
ff1d71c2f8 | ||
|
|
81b5123294 | ||
|
|
7a89a3cd9c | ||
|
|
08fd234e60 | ||
|
|
0fac2dd939 | ||
|
|
212e7843ec | ||
|
|
6bbf0c02bd | ||
|
|
2485bed350 | ||
|
|
2e517c985d | ||
|
|
75303b5eb8 | ||
|
|
b5997aa70e | ||
|
|
07e26915b0 | ||
|
|
4661c6cb77 | ||
|
|
0d1e0d730d | ||
|
|
7ef0db545c | ||
|
|
9f6316976c | ||
|
|
0a01ddb430 | ||
|
|
c40b17bd4c | ||
|
|
858fa6835c | ||
|
|
96babc454d | ||
|
|
8579ff39b1 | ||
|
|
99163d90d2 | ||
|
|
8a709158d6 | ||
|
|
25cf4cfd22 | ||
|
|
9f8e937766 | ||
|
|
9d5a57521e | ||
|
|
e36a1ffea0 | ||
|
|
b05c793fe9 | ||
|
|
c45417623c | ||
|
|
6704c6dd4e | ||
|
|
87cd31ae5d | ||
|
|
8755001f7e | ||
|
|
7c2e832065 | ||
|
|
096d82f7af | ||
|
|
8c8f045b9a | ||
|
|
b3140f1623 | ||
|
|
c5e104f90b | ||
|
|
09b8f00eed | ||
|
|
c39c72da7a | ||
|
|
7f28876047 | ||
|
|
24ff87c306 | ||
|
|
e8b758214d | ||
|
|
e58c563795 | ||
|
|
328bbb797d | ||
|
|
dab4f678f0 | ||
|
|
c847cc1d16 | ||
|
|
f49ba6bd4d | ||
|
|
0309912f98 | ||
|
|
0c253dc46a | ||
|
|
4b081890fd | ||
|
|
87f86693a6 | ||
|
|
bc12663c7f | ||
|
|
f793673875 | ||
|
|
dcc4353865 | ||
|
|
b9e6dc4c2b |
@@ -3,7 +3,7 @@ name: Desktop Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/*desktop*'
|
||||
- '*standardnotes/*web*'
|
||||
- '!*standardnotes/*inner-desktop*'
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ jobs:
|
||||
cache: 'yarn'
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: Compile Mac
|
||||
run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- name: MacX64
|
||||
run: |
|
||||
yarn run electron-builder --mac --x64 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
node scripts/fixMacZip.js
|
||||
node scripts/fixMacZip.js ${{ env.APP_VERSION }}
|
||||
- name: MacArm64
|
||||
run: yarn run electron-builder --mac --arm64 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: Compile for AppImage
|
||||
run: yarn run webpack --config desktop.webpack.prod.js
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
- run: yarn run electron-builder --windows --x64 --ia32 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
- name: Upload
|
||||
@@ -160,6 +160,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -167,6 +168,8 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
@@ -194,8 +197,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
tag_name: "@standardnotes/desktop@${{ steps.package-version.outputs.current-version}}"
|
||||
name: "Desktop ${{ steps.package-version.outputs.current-version }}"
|
||||
tag_name: "@standardnotes/desktop@${{ env.APP_VERSION }}"
|
||||
name: "Desktop ${{ env.APP_VERSION }}"
|
||||
body: ${{ steps.release-notes.outputs.result }}
|
||||
prerelease: true
|
||||
draft: false
|
||||
@@ -214,4 +217,4 @@ jobs:
|
||||
sudo snap install snapcraft --classic
|
||||
echo "${{ secrets.SNAPCRAFT_LOGIN_FILE }}" >> snapauth.txt
|
||||
snapcraft login --with=snapauth.txt
|
||||
snapcraft upload dist/standard-notes-${{ steps.package-version.outputs.current-version}}-linux-amd64.snap --release stable,candidate,beta,edge
|
||||
snapcraft upload dist/standard-notes-${{ env.APP_VERSION }}-linux-amd64.snap --release stable,candidate,beta,edge
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
- run: yarn run electron-builder --windows --x64 --ia32 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
- name: Upload
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Mobile Release Production
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/mobile*'
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Production Android keystore
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable && yarn install:pods
|
||||
- run: yarn build:mobile
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Mobile TestFlight
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/mobile*'
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Dev Android keystore
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -45,10 +45,18 @@ jobs:
|
||||
aws-region: us-east-1
|
||||
- name: Deploy static site to S3 bucket
|
||||
run: aws s3 sync packages/web/dist/ s3://app.standardnotes.com --delete
|
||||
- name: Invalidate CloudFront Cache
|
||||
- name: Invalidate CloudFront Cache on .COM
|
||||
uses: chetan/invalidate-cloudfront-action@master
|
||||
env:
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_DISTRIBUTION_ID }}
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_COM_DISTRIBUTION_ID }}
|
||||
PATHS: '/*'
|
||||
AWS_REGION: 'us-east-1'
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
- name: Invalidate CloudFront Cache on .ORG
|
||||
uses: chetan/invalidate-cloudfront-action@master
|
||||
env:
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_ORG_DISTRIBUTION_ID }}
|
||||
PATHS: '/*'
|
||||
AWS_REGION: 'us-east-1'
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
|
||||
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.
+2
-1
@@ -31,7 +31,8 @@
|
||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
"version": "yarn install --no-immutable && git add yarn.lock",
|
||||
"version": "yarn install --no-immutable && git add yarn.lock && yarn changelog:json",
|
||||
"changelog:json": "node scripts/ChangelogToJson.js && git add .",
|
||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||
"workspace:list": " yarn lerna list -all",
|
||||
"upgrade:snjs": "ncu -u '@standardnotes/*' && yarn workspaces foreach --verbose run upgrade:snjs"
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.20.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.6",
|
||||
"version": "1.20.18",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
|
||||
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ProtocolVersion, UserRequestType } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UserRequestType, Uuid } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
export type SubscriptionInviteListResponseBody = {
|
||||
invitations: Array<Invitation>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
|
||||
@@ -3,6 +3,80 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve variance between Super header font sizes and weights ([8b10330](https://github.com/standardnotes/app/commit/8b103307cba41855713b8627cf5d66a6fef6ff52))
|
||||
|
||||
## [1.9.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Disable all-caps for H2/H3 headings in Super notes ([9b3f11d](https://github.com/standardnotes/app/commit/9b3f11d85b10914a03aab93803513766670c47fe))
|
||||
* Super note block drag-n-drop on mobile ([#2072](https://github.com/standardnotes/app/issues/2072)) ([349fd72](https://github.com/standardnotes/app/commit/349fd7204e209d56b720a3b0cc6b2bfc0fc50f47))
|
||||
|
||||
## [1.9.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.9.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.9.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make super note styling consistent with plaintext notes ([#2068](https://github.com/standardnotes/app/issues/2068)) ([a417e5b](https://github.com/standardnotes/app/commit/a417e5ba8d3fc224b1527d0a9cfb0f25d197f9de))
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Only autofocus super notes when creating new note ([#2063](https://github.com/standardnotes/app/issues/2063)) ([96e8dfd](https://github.com/standardnotes/app/commit/96e8dfdd310fad88a360c4ac984b376b51a618d1))
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
# [1.9.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* dim checked list items in super editor ([#2046](https://github.com/standardnotes/app/issues/2046)) ([8a70915](https://github.com/standardnotes/app/commit/8a709158d6839b9eb1092923a14b0dcadb0da25b))
|
||||
* option to show markdown preview for super notes (skip e2e) ([#2048](https://github.com/standardnotes/app/issues/2048)) ([8579ff3](https://github.com/standardnotes/app/commit/8579ff39b1f81dbbcf22b4031ede183ab4287262))
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* use line height preference in super editor (skip e2e) ([#2045](https://github.com/standardnotes/app/issues/2045)) ([87cd31a](https://github.com/standardnotes/app/commit/87cd31ae5d630a528ce6d4895b2c144bf51808f0))
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
### Features
|
||||
|
||||
* ability to drag super list items; secure password generation blocks ([#2039](https://github.com/standardnotes/app/issues/2039)) ([c39c72d](https://github.com/standardnotes/app/commit/c39c72da7a4fb85f4da9aa4e6f8e9f7ba4486a94))
|
||||
|
||||
## [1.6.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improved error reporting in super; improved draggable block icon padding ([24ff87c](https://github.com/standardnotes/app/commit/24ff87c30625432393ffedeada14478ebb625292))
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue where converting empty plain note to super would result in error ([e58c563](https://github.com/standardnotes/app/commit/e58c5637951f928efcd8c8427f06d0b6c69b5e55))
|
||||
|
||||
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **web:** enable block drag'n'drop in super editor ([#2029](https://github.com/standardnotes/app/issues/2029)) ([dab4f67](https://github.com/standardnotes/app/commit/dab4f678f04aee2c7dfbe7f416e7fc34e8d870cf))
|
||||
|
||||
## [1.5.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/blocks-editor",
|
||||
"version": "1.5.3",
|
||||
"version": "1.9.7",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,17 +2,10 @@ import {FunctionComponent, useCallback, useState} from 'react';
|
||||
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
|
||||
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
|
||||
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
|
||||
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
|
||||
import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin';
|
||||
import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin';
|
||||
import {MarkdownShortcutPlugin} from '@lexical/react/LexicalMarkdownShortcutPlugin';
|
||||
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
|
||||
import {
|
||||
CHECK_LIST,
|
||||
ELEMENT_TRANSFORMERS,
|
||||
TEXT_FORMAT_TRANSFORMERS,
|
||||
TEXT_MATCH_TRANSFORMERS,
|
||||
} from '@lexical/markdown';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin';
|
||||
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
|
||||
@@ -30,16 +23,17 @@ import FloatingTextFormatToolbarPlugin from '../Lexical/Plugins/FloatingTextForm
|
||||
import FloatingLinkEditorPlugin from '../Lexical/Plugins/FloatingLinkEditorPlugin';
|
||||
import {truncateString} from './Utils';
|
||||
import {SuperEditorContentId} from './Constants';
|
||||
|
||||
const BlockDragEnabled = false;
|
||||
import {classNames} from '@standardnotes/utils';
|
||||
import {MarkdownTransformers} from './MarkdownTransformers';
|
||||
|
||||
type BlocksEditorProps = {
|
||||
onChange: (value: string, preview: string) => void;
|
||||
onChange?: (value: string, preview: string) => void;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
previewLength: number;
|
||||
previewLength?: number;
|
||||
spellcheck?: boolean;
|
||||
ignoreFirstChange?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
@@ -49,6 +43,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewLength,
|
||||
spellcheck,
|
||||
ignoreFirstChange = false,
|
||||
readonly,
|
||||
}) => {
|
||||
const [didIgnoreFirstChange, setDidIgnoreFirstChange] = useState(false);
|
||||
const handleChange = useCallback(
|
||||
@@ -67,10 +62,19 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewText += '\n';
|
||||
}
|
||||
});
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange(stringifiedEditorState, previewText);
|
||||
if (previewLength) {
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
}
|
||||
|
||||
try {
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange?.(stringifiedEditorState, previewText);
|
||||
} catch (error) {
|
||||
window.alert(
|
||||
`An invalid change was made inside the Super editor. Your change was not saved. Please report this error to the team: ${error}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
[onChange, didIgnoreFirstChange],
|
||||
@@ -94,7 +98,10 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<div className="editor" ref={onRef}>
|
||||
<ContentEditable
|
||||
id={SuperEditorContentId}
|
||||
className={`ContentEditable__root ${className}`}
|
||||
className={classNames(
|
||||
'ContentEditable__root overflow-y-auto',
|
||||
className,
|
||||
)}
|
||||
spellCheck={spellcheck}
|
||||
/>
|
||||
</div>
|
||||
@@ -104,19 +111,11 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ListPlugin />
|
||||
<MarkdownShortcutPlugin
|
||||
transformers={[
|
||||
CHECK_LIST,
|
||||
...ELEMENT_TRANSFORMERS,
|
||||
...TEXT_FORMAT_TRANSFORMERS,
|
||||
...TEXT_MATCH_TRANSFORMERS,
|
||||
]}
|
||||
/>
|
||||
<MarkdownShortcutPlugin transformers={MarkdownTransformers} />
|
||||
<TablePlugin />
|
||||
<OnChangePlugin onChange={handleChange} ignoreSelectionChange={true} />
|
||||
<HistoryPlugin />
|
||||
<HorizontalRulePlugin />
|
||||
<AutoFocusPlugin />
|
||||
<ClearEditorPlugin />
|
||||
<CheckListPlugin />
|
||||
<CodeHighlightPlugin />
|
||||
@@ -126,15 +125,13 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<TwitterPlugin />
|
||||
<YouTubePlugin />
|
||||
<CollapsiblePlugin />
|
||||
{floatingAnchorElem && (
|
||||
{!readonly && floatingAnchorElem && (
|
||||
<>
|
||||
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
|
||||
<FloatingLinkEditorPlugin />
|
||||
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
|
||||
</>
|
||||
)}
|
||||
{floatingAnchorElem && BlockDragEnabled && (
|
||||
<>{<DraggableBlockPlugin anchorElem={floatingAnchorElem} />}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import {BlockEditorNodes} from '../Lexical/Nodes/AllNodes';
|
||||
import {Klass, LexicalNode} from 'lexical';
|
||||
|
||||
type BlocksEditorComposerProps = {
|
||||
initialValue: string;
|
||||
initialValue: string | undefined;
|
||||
children: React.ReactNode;
|
||||
nodes?: Array<Klass<LexicalNode>>;
|
||||
readonly?: boolean;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
CHECK_LIST,
|
||||
ELEMENT_TRANSFORMERS,
|
||||
TEXT_FORMAT_TRANSFORMERS,
|
||||
TEXT_MATCH_TRANSFORMERS,
|
||||
} from '@lexical/markdown';
|
||||
|
||||
export const MarkdownTransformers = [
|
||||
CHECK_LIST,
|
||||
...ELEMENT_TRANSFORMERS,
|
||||
...TEXT_FORMAT_TRANSFORMERS,
|
||||
...TEXT_MATCH_TRANSFORMERS,
|
||||
];
|
||||
@@ -1,19 +1,21 @@
|
||||
.draggable-block-menu {
|
||||
border-radius: 4px;
|
||||
padding: 2px 1px;
|
||||
padding: 3px 1px;
|
||||
cursor: grab;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
will-change: transform;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.draggable-block-menu .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.3;
|
||||
background-image: url(#{$blocks-editor-icons-path}/draggable-block-menu.svg);
|
||||
width: 0.8rem;
|
||||
height: 1.1rem;
|
||||
opacity: 0.2;
|
||||
padding-left: 4.75px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.draggable-block-menu:active {
|
||||
@@ -21,16 +23,17 @@
|
||||
}
|
||||
|
||||
.draggable-block-menu:hover {
|
||||
background-color: #efefef;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
}
|
||||
|
||||
.draggable-block-target-line {
|
||||
pointer-events: none;
|
||||
background: deepskyblue;
|
||||
height: 4px;
|
||||
background: var(--sn-stylekit-info-color);
|
||||
height: 0.25rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
will-change: transform;
|
||||
will-change: transform, opacity;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
import {$createListNode, $isListNode} from '@lexical/list';
|
||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||
import {eventFiles} from '@lexical/rich-text';
|
||||
import {mergeRegister} from '@lexical/utils';
|
||||
@@ -16,19 +17,28 @@ import {
|
||||
DRAGOVER_COMMAND,
|
||||
DROP_COMMAND,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
} from 'lexical';
|
||||
import {DragEvent as ReactDragEvent, useEffect, useRef, useState} from 'react';
|
||||
import {
|
||||
DragEvent as ReactDragEvent,
|
||||
TouchEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
import {BlockIcon} from '@standardnotes/icons';
|
||||
|
||||
import {isHTMLElement} from '../../Utils/guard';
|
||||
import {Point} from '../../Utils/point';
|
||||
import {Rect} from '../../Utils/rect';
|
||||
import {ContainsPointReturn, Rect} from '../../Utils/rect';
|
||||
|
||||
const SPACE = 4;
|
||||
const DRAGGABLE_BLOCK_MENU_LEFT_SPACE = -2;
|
||||
const TARGET_LINE_HALF_HEIGHT = 2;
|
||||
const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
|
||||
const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
|
||||
const TEXT_BOX_HORIZONTAL_PADDING = 28;
|
||||
const TEXT_BOX_HORIZONTAL_PADDING = 24;
|
||||
|
||||
const Downward = 1;
|
||||
const Upward = -1;
|
||||
@@ -52,12 +62,55 @@ function getTopLevelNodeKeys(editor: LexicalEditor): string[] {
|
||||
return root ? root.__children : [];
|
||||
}
|
||||
|
||||
function elementContainingEventLocation(
|
||||
anchorElem: HTMLElement,
|
||||
element: HTMLElement,
|
||||
eventLocation: Point,
|
||||
): {contains: ContainsPointReturn; element: HTMLElement} {
|
||||
const anchorElementRect = anchorElem.getBoundingClientRect();
|
||||
|
||||
const elementDomRect = Rect.fromDOM(element);
|
||||
const {marginTop, marginBottom} = window.getComputedStyle(element);
|
||||
|
||||
const rect = elementDomRect.generateNewRect({
|
||||
bottom: elementDomRect.bottom + parseFloat(marginBottom),
|
||||
left: anchorElementRect.left,
|
||||
right: anchorElementRect.right,
|
||||
top: elementDomRect.top - parseFloat(marginTop),
|
||||
});
|
||||
|
||||
const children = Array.from(element.children);
|
||||
|
||||
const shouldRecurseIntoChildren = ['UL', 'OL', 'LI'].includes(
|
||||
element.tagName,
|
||||
);
|
||||
|
||||
if (shouldRecurseIntoChildren) {
|
||||
for (const child of children) {
|
||||
const isLeaf = child.children.length === 0;
|
||||
if (isLeaf) {
|
||||
continue;
|
||||
}
|
||||
const childResult = elementContainingEventLocation(
|
||||
anchorElem,
|
||||
child as HTMLElement,
|
||||
eventLocation,
|
||||
);
|
||||
|
||||
if (childResult.contains.result) {
|
||||
return childResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {contains: rect.contains(eventLocation), element: element};
|
||||
}
|
||||
|
||||
function getBlockElement(
|
||||
anchorElem: HTMLElement,
|
||||
editor: LexicalEditor,
|
||||
event: MouseEvent,
|
||||
eventLocation: Point,
|
||||
): HTMLElement | null {
|
||||
const anchorElementRect = anchorElem.getBoundingClientRect();
|
||||
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
|
||||
|
||||
let blockElem: HTMLElement | null = null;
|
||||
@@ -72,32 +125,22 @@ function getBlockElement(
|
||||
if (elem === null) {
|
||||
break;
|
||||
}
|
||||
const point = new Point(event.x, event.y);
|
||||
const domRect = Rect.fromDOM(elem);
|
||||
const {marginTop, marginBottom} = window.getComputedStyle(elem);
|
||||
const {contains, element} = elementContainingEventLocation(
|
||||
anchorElem,
|
||||
elem,
|
||||
eventLocation,
|
||||
);
|
||||
|
||||
const rect = domRect.generateNewRect({
|
||||
bottom: domRect.bottom + parseFloat(marginBottom),
|
||||
left: anchorElementRect.left,
|
||||
right: anchorElementRect.right,
|
||||
top: domRect.top - parseFloat(marginTop),
|
||||
});
|
||||
|
||||
const {
|
||||
result,
|
||||
reason: {isOnTopSide, isOnBottomSide},
|
||||
} = rect.contains(point);
|
||||
|
||||
if (result) {
|
||||
blockElem = elem;
|
||||
if (contains.result) {
|
||||
blockElem = element;
|
||||
prevIndex = index;
|
||||
break;
|
||||
}
|
||||
|
||||
if (direction === Indeterminate) {
|
||||
if (isOnTopSide) {
|
||||
if (contains.reason.isOnTopSide) {
|
||||
direction = Upward;
|
||||
} else if (isOnBottomSide) {
|
||||
} else if (contains.reason.isOnBottomSide) {
|
||||
direction = Downward;
|
||||
} else {
|
||||
// stop search block element
|
||||
@@ -123,7 +166,6 @@ function setMenuPosition(
|
||||
) {
|
||||
if (!targetElem) {
|
||||
floatingElem.style.opacity = '0';
|
||||
floatingElem.style.transform = 'translate(-10000px, -10000px)';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,7 +179,7 @@ function setMenuPosition(
|
||||
(parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
|
||||
anchorElementRect.top;
|
||||
|
||||
const left = SPACE;
|
||||
const left = DRAGGABLE_BLOCK_MENU_LEFT_SPACE;
|
||||
|
||||
floatingElem.style.opacity = '1';
|
||||
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
|
||||
@@ -179,19 +221,19 @@ function setTargetLine(
|
||||
}
|
||||
|
||||
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
|
||||
const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE;
|
||||
const left = TEXT_BOX_HORIZONTAL_PADDING - DRAGGABLE_BLOCK_MENU_LEFT_SPACE;
|
||||
|
||||
targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
|
||||
targetLineElem.style.width = `${
|
||||
anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2
|
||||
anchorWidth -
|
||||
(TEXT_BOX_HORIZONTAL_PADDING - DRAGGABLE_BLOCK_MENU_LEFT_SPACE) * 2
|
||||
}px`;
|
||||
targetLineElem.style.opacity = '.4';
|
||||
targetLineElem.style.opacity = '.6';
|
||||
}
|
||||
|
||||
function hideTargetLine(targetLineElem: HTMLElement | null) {
|
||||
if (targetLineElem) {
|
||||
targetLineElem.style.opacity = '0';
|
||||
targetLineElem.style.transform = 'translate(-10000px, -10000px)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +248,7 @@ function useDraggableBlockMenu(
|
||||
const targetLineRef = useRef<HTMLDivElement>(null);
|
||||
const [draggableBlockElem, setDraggableBlockElem] =
|
||||
useState<HTMLElement | null>(null);
|
||||
const dragDataRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
@@ -219,7 +262,11 @@ function useDraggableBlockMenu(
|
||||
return;
|
||||
}
|
||||
|
||||
const _draggableBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
const _draggableBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.clientX, event.clientY),
|
||||
);
|
||||
|
||||
setDraggableBlockElem(_draggableBlockElem);
|
||||
}
|
||||
@@ -243,6 +290,34 @@ function useDraggableBlockMenu(
|
||||
}
|
||||
}, [anchorElem, draggableBlockElem]);
|
||||
|
||||
const insertDraggedNode = useCallback(
|
||||
(
|
||||
draggedNode: LexicalNode,
|
||||
targetNode: LexicalNode,
|
||||
targetBlockElem: HTMLElement,
|
||||
pageY: number,
|
||||
) => {
|
||||
let nodeToInsert = draggedNode;
|
||||
const targetParent = targetNode.getParent();
|
||||
const sourceParent = draggedNode.getParent();
|
||||
|
||||
if ($isListNode(sourceParent) && !$isListNode(targetParent)) {
|
||||
const newList = $createListNode(sourceParent.getListType());
|
||||
newList.append(draggedNode);
|
||||
nodeToInsert = newList;
|
||||
}
|
||||
|
||||
const {top, height} = targetBlockElem.getBoundingClientRect();
|
||||
const shouldInsertAfter = pageY - top > height / 2;
|
||||
if (shouldInsertAfter) {
|
||||
targetNode.insertAfter(nodeToInsert);
|
||||
} else {
|
||||
targetNode.insertBefore(nodeToInsert);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function onDragover(event: DragEvent): boolean {
|
||||
const [isFileTransfer] = eventFiles(event);
|
||||
@@ -253,7 +328,11 @@ function useDraggableBlockMenu(
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
const targetBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.pageX, pageY),
|
||||
);
|
||||
const targetLineElem = targetLineRef.current;
|
||||
if (targetBlockElem === null || targetLineElem === null) {
|
||||
return false;
|
||||
@@ -269,19 +348,27 @@ function useDraggableBlockMenu(
|
||||
if (isFileTransfer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {target, dataTransfer, pageY} = event;
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || '';
|
||||
const draggedNode = $getNodeByKey(dragData);
|
||||
if (!draggedNode) {
|
||||
return false;
|
||||
}
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
const targetBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.pageX, pageY),
|
||||
);
|
||||
if (!targetBlockElem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
|
||||
if (!targetNode) {
|
||||
return false;
|
||||
@@ -289,13 +376,9 @@ function useDraggableBlockMenu(
|
||||
if (targetNode === draggedNode) {
|
||||
return true;
|
||||
}
|
||||
const {top, height} = targetBlockElem.getBoundingClientRect();
|
||||
const shouldInsertAfter = pageY - top > height / 2;
|
||||
if (shouldInsertAfter) {
|
||||
targetNode.insertAfter(draggedNode);
|
||||
} else {
|
||||
targetNode.insertBefore(draggedNode);
|
||||
}
|
||||
|
||||
insertDraggedNode(draggedNode, targetNode, targetBlockElem, event.pageY);
|
||||
|
||||
setDraggableBlockElem(null);
|
||||
|
||||
return true;
|
||||
@@ -317,7 +400,7 @@ function useDraggableBlockMenu(
|
||||
COMMAND_PRIORITY_HIGH,
|
||||
),
|
||||
);
|
||||
}, [anchorElem, editor]);
|
||||
}, [anchorElem, editor, insertDraggedNode]);
|
||||
|
||||
function onDragStart(event: ReactDragEvent<HTMLDivElement>): void {
|
||||
const dataTransfer = event.dataTransfer;
|
||||
@@ -339,6 +422,79 @@ function useDraggableBlockMenu(
|
||||
hideTargetLine(targetLineRef.current);
|
||||
}
|
||||
|
||||
function onTouchStart(): void {
|
||||
if (!draggableBlockElem) {
|
||||
return;
|
||||
}
|
||||
editor.update(() => {
|
||||
const node = $getNearestNodeFromDOMNode(draggableBlockElem);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const nodeKey = node.getKey();
|
||||
dragDataRef.current = nodeKey;
|
||||
});
|
||||
}
|
||||
|
||||
function onTouchMove(event: TouchEvent) {
|
||||
const {pageX, pageY} = event.targetTouches[0];
|
||||
const rootElement = editor.getRootElement();
|
||||
if (rootElement) {
|
||||
const {top, bottom} = rootElement.getBoundingClientRect();
|
||||
const scrollOffset = 20;
|
||||
if (pageY - top < scrollOffset) {
|
||||
rootElement.scrollTop -= scrollOffset;
|
||||
} else if (bottom - pageY < scrollOffset) {
|
||||
rootElement.scrollTop += scrollOffset;
|
||||
}
|
||||
}
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(pageX, pageY),
|
||||
);
|
||||
const targetLineElem = targetLineRef.current;
|
||||
if (targetBlockElem === null || targetLineElem === null) {
|
||||
return;
|
||||
}
|
||||
setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
|
||||
}
|
||||
|
||||
function onTouchEnd(event: TouchEvent): void {
|
||||
hideTargetLine(targetLineRef.current);
|
||||
|
||||
editor.update(() => {
|
||||
const {pageX, pageY} = event.changedTouches[0];
|
||||
|
||||
const dragData = dragDataRef.current || '';
|
||||
const draggedNode = $getNodeByKey(dragData);
|
||||
if (!draggedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(pageX, pageY),
|
||||
);
|
||||
if (!targetBlockElem) {
|
||||
return;
|
||||
}
|
||||
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
|
||||
|
||||
if (!targetNode) {
|
||||
return;
|
||||
}
|
||||
if (targetNode === draggedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
insertDraggedNode(draggedNode, targetNode, targetBlockElem, pageY);
|
||||
});
|
||||
|
||||
setDraggableBlockElem(null);
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<div
|
||||
@@ -346,8 +502,13 @@ function useDraggableBlockMenu(
|
||||
ref={menuRef}
|
||||
draggable={true}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}>
|
||||
<div className={isEditable ? 'icon' : ''} />
|
||||
onDragEnd={onDragEnd}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}>
|
||||
<div className={isEditable ? 'icon' : ''}>
|
||||
<BlockIcon className="text-text pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="draggable-block-target-line" ref={targetLineRef} />
|
||||
</>,
|
||||
|
||||
@@ -110,8 +110,8 @@ export function InsertTableDialog({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextInput label="No of rows" onChange={setRows} value={rows} />
|
||||
<TextInput label="No of columns" onChange={setColumns} value={columns} />
|
||||
<TextInput label="Number of rows" onChange={setRows} value={rows} />
|
||||
<TextInput label="Number of columns" onChange={setColumns} value={columns} />
|
||||
<DialogActions data-test-id="table-model-confirm-insert">
|
||||
<Button onClick={onClick}>Confirm</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -20,22 +20,21 @@
|
||||
padding-left: 16px;
|
||||
}
|
||||
.Lexical__h1 {
|
||||
font-size: 24px;
|
||||
font-size: 26px;
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
font-weight: 400;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
.Lexical__h2 {
|
||||
font-size: 15px;
|
||||
font-size: 22px;
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Lexical__h3 {
|
||||
font-size: 12px;
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Lexical__textBold {
|
||||
font-weight: bold;
|
||||
@@ -325,6 +324,7 @@
|
||||
}
|
||||
.Lexical__listItemChecked {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.Lexical__listItemUnchecked:before,
|
||||
.Lexical__listItemChecked:before {
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border: 0px;
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.Button__root:hover {
|
||||
background-color: #ddd;
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
.Button__small {
|
||||
padding-top: 5px;
|
||||
@@ -32,5 +32,5 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.Button__disabled:hover {
|
||||
background-color: #eee;
|
||||
background-color: var(--sn-stylekit-secondary-background-color);
|
||||
}
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
color: #666;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.Input__input {
|
||||
display: flex;
|
||||
flex: 2;
|
||||
border: 1px solid #999;
|
||||
border: 1px solid var(--sn-stylekit-contrast-border-color);
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: rgba(40, 40, 40, 0.6);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
flex-grow: 0px;
|
||||
flex-shrink: 1px;
|
||||
z-index: 100;
|
||||
@@ -28,22 +28,23 @@
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
flex-grow: 0px;
|
||||
background-color: #fff;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
box-shadow: 0 0 20px 0 #444;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0px 0 var(--sn-stylekit-shadow-color);
|
||||
border-radius: 0px;
|
||||
}
|
||||
.Modal__title {
|
||||
color: #444;
|
||||
color:var(--sn-stylekit-foreground-color);
|
||||
margin: 0px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
||||
}
|
||||
.Modal__closeButton {
|
||||
border: 0px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 15px;
|
||||
border-radius: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -52,10 +53,11 @@
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background-color: #eee;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
}
|
||||
.Modal__closeButton:hover {
|
||||
background-color: #ddd;
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
.Modal__content {
|
||||
padding-top: 20px;
|
||||
|
||||
@@ -73,7 +73,7 @@ function PortalImpl({
|
||||
aria-label="Close modal"
|
||||
type="button"
|
||||
onClick={onClose}>
|
||||
X
|
||||
✕
|
||||
</button>
|
||||
<div className="Modal__content">{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {isPoint, Point} from './point';
|
||||
|
||||
type ContainsPointReturn = {
|
||||
export type ContainsPointReturn = {
|
||||
result: boolean;
|
||||
reason: {
|
||||
isOnTopSide: boolean;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Editor/BlocksEditor';
|
||||
export * from './Editor/BlocksEditorComposer';
|
||||
export * from './Editor/Constants';
|
||||
export * from './Editor/MarkdownTransformers';
|
||||
|
||||
@@ -3,6 +3,218 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.104.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.104.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [3.103.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.103.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.103.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
### Features
|
||||
|
||||
* screen presentation and dismiss animations for mobile ([#2073](https://github.com/standardnotes/app/issues/2073)) ([7f2074a](https://github.com/standardnotes/app/commit/7f2074a6ec59068f75dae76d31721d65e61c9673))
|
||||
|
||||
## [3.102.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.102.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [3.101.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.101.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.101.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
|
||||
|
||||
## [3.100.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,10 @@
|
||||
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import {
|
||||
FileBackupRecord,
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { AppState } from 'app/AppState'
|
||||
import { shell } from 'electron'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
@@ -6,13 +12,14 @@ import path from 'path'
|
||||
import {
|
||||
deleteFile,
|
||||
ensureDirectoryExists,
|
||||
moveFiles,
|
||||
moveDirContents,
|
||||
openDirectoryPicker,
|
||||
readJSONFile,
|
||||
writeFile,
|
||||
writeJSONFile,
|
||||
} from '../Utils/FileUtils'
|
||||
import { FileDownloader } from './FileDownloader'
|
||||
import { FileReadOperation } from './FileReadOperation'
|
||||
|
||||
export const FileBackupsConstantsV1 = {
|
||||
Version: '1.0.0',
|
||||
@@ -21,6 +28,8 @@ export const FileBackupsConstantsV1 = {
|
||||
}
|
||||
|
||||
export class FilesBackupManager implements FileBackupsDevice {
|
||||
private readOperations: Map<string, FileReadOperation> = new Map()
|
||||
|
||||
constructor(private appState: AppState) {}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
@@ -78,8 +87,11 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
}
|
||||
|
||||
const entries = Object.values(mapping.files)
|
||||
const itemFolders = entries.map((entry) => path.join(oldPath, entry.relativePath))
|
||||
await moveFiles(itemFolders, newPath)
|
||||
for (const entry of entries) {
|
||||
const sourcePath = path.join(oldPath, entry.relativePath)
|
||||
const destinationPath = path.join(newPath, entry.relativePath)
|
||||
await moveDirContents(sourcePath, destinationPath)
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
entry.absolutePath = path.join(newPath, entry.relativePath)
|
||||
@@ -120,6 +132,10 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
return this.defaultMappingFileValue()
|
||||
}
|
||||
|
||||
for (const entry of Object.values(data.files)) {
|
||||
entry.backedUpOn = new Date(entry.backedUpOn)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -129,6 +145,10 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
void shell.openPath(location)
|
||||
}
|
||||
|
||||
async openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
void shell.openPath(record.absolutePath)
|
||||
}
|
||||
|
||||
async saveFilesBackupsMappingFile(file: FileBackupsMapping): Promise<'success' | 'failed'> {
|
||||
await writeJSONFile(this.getMappingFileLocation(), file)
|
||||
|
||||
@@ -180,4 +200,28 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
const operation = new FileReadOperation(record)
|
||||
|
||||
this.readOperations.set(operation.token, operation)
|
||||
|
||||
return operation.token
|
||||
}
|
||||
|
||||
async readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
|
||||
const operation = this.readOperations.get(token)
|
||||
|
||||
if (!operation) {
|
||||
return Promise.reject(new Error('Invalid token'))
|
||||
}
|
||||
|
||||
const result = await operation.readNextChunk()
|
||||
|
||||
if (result.isLast) {
|
||||
this.readOperations.delete(token)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { FileBackupReadChunkResponse, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const ONE_MB = 1024 * 1024
|
||||
const CHUNK_LIMIT = ONE_MB * 5
|
||||
|
||||
export class FileReadOperation {
|
||||
public readonly token: string
|
||||
private currentChunkLocation = 0
|
||||
private localFileId: number
|
||||
private fileLength: number
|
||||
|
||||
constructor(backupRecord: FileBackupRecord) {
|
||||
this.token = backupRecord.absolutePath
|
||||
this.localFileId = fs.openSync(path.join(backupRecord.absolutePath, backupRecord.binaryFileName), 'r')
|
||||
this.fileLength = fs.fstatSync(this.localFileId).size
|
||||
}
|
||||
|
||||
async readNextChunk(): Promise<FileBackupReadChunkResponse> {
|
||||
let isLast = false
|
||||
let readUpto = this.currentChunkLocation + CHUNK_LIMIT
|
||||
if (readUpto > this.fileLength) {
|
||||
readUpto = this.fileLength
|
||||
isLast = true
|
||||
}
|
||||
|
||||
const readLength = readUpto - this.currentChunkLocation
|
||||
|
||||
const chunk = await this.readChunk(this.currentChunkLocation, readLength)
|
||||
|
||||
this.currentChunkLocation = readUpto
|
||||
|
||||
if (isLast) {
|
||||
fs.close(this.localFileId)
|
||||
}
|
||||
|
||||
return {
|
||||
chunk,
|
||||
isLast,
|
||||
progress: {
|
||||
encryptedFileSize: this.fileLength,
|
||||
encryptedBytesDownloaded: this.currentChunkLocation,
|
||||
encryptedBytesRemaining: this.fileLength - this.currentChunkLocation,
|
||||
percentComplete: (this.currentChunkLocation / this.fileLength) * 100.0,
|
||||
source: 'local',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async readChunk(start: number, length: number): Promise<Uint8Array> {
|
||||
const buffer = Buffer.alloc(length)
|
||||
|
||||
fs.readSync(this.localFileId, buffer, 0, length, start)
|
||||
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MediaManagerInterface } from './MediaManagerInterface'
|
||||
|
||||
const { systemPreferences } = require('electron')
|
||||
|
||||
export class MediaManager implements MediaManagerInterface {
|
||||
async askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
const permission = systemPreferences.getMediaAccessStatus(type)
|
||||
|
||||
if (permission === 'granted') {
|
||||
return true
|
||||
} else if (permission === 'denied') {
|
||||
return false
|
||||
}
|
||||
|
||||
const granted = await systemPreferences.askForMediaAccess(type)
|
||||
return granted
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface MediaManagerInterface {
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
@@ -5,7 +5,13 @@ import { StoreKeys } from '../Store/StoreKeys'
|
||||
const path = require('path')
|
||||
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
|
||||
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import {
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import { BackupsManagerInterface } from '../Backups/BackupsManagerInterface'
|
||||
import { KeychainInterface } from '../Keychain/KeychainInterface'
|
||||
@@ -13,6 +19,7 @@ import { MenuManagerInterface } from '../Menus/MenuManagerInterface'
|
||||
import { Component, PackageManagerInterface } from '../Packages/PackageManagerInterface'
|
||||
import { SearchManagerInterface } from '../Search/SearchManagerInterface'
|
||||
import { RemoteDataInterface } from './DataInterface'
|
||||
import { MediaManagerInterface } from '../Media/MediaManagerInterface'
|
||||
|
||||
/**
|
||||
* Read https://github.com/electron/remote to understand how electron/remote works.
|
||||
@@ -28,6 +35,7 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
private data: RemoteDataInterface,
|
||||
private menus: MenuManagerInterface,
|
||||
private fileBackups: FileBackupsDevice,
|
||||
private media: MediaManagerInterface,
|
||||
) {}
|
||||
|
||||
get exposableValue(): CrossProcessBridge {
|
||||
@@ -64,6 +72,10 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
changeFilesBackupsLocation: this.changeFilesBackupsLocation.bind(this),
|
||||
getFilesBackupsLocation: this.getFilesBackupsLocation.bind(this),
|
||||
openFilesBackupsLocation: this.openFilesBackupsLocation.bind(this),
|
||||
openFileBackup: this.openFileBackup.bind(this),
|
||||
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
|
||||
readNextChunk: this.readNextChunk.bind(this),
|
||||
askForMediaAccess: this.askForMediaAccess.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +191,14 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
return this.fileBackups.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.fileBackups.getFileBackupReadToken(record)
|
||||
}
|
||||
|
||||
readNextChunk(nextToken: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.fileBackups.readNextChunk(nextToken)
|
||||
}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.fileBackups.isFilesBackupsEnabled()
|
||||
}
|
||||
@@ -202,4 +222,12 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
public openFilesBackupsLocation(): Promise<void> {
|
||||
return this.fileBackups.openFilesBackupsLocation()
|
||||
}
|
||||
|
||||
public openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
return this.fileBackups.openFileBackup(record)
|
||||
}
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
return this.media.askForMediaAccess(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { removeFromArray } from '../Utils/Utils'
|
||||
|
||||
export const FileDoesNotExist = 'ENOENT'
|
||||
export const FileAlreadyExists = 'EEXIST'
|
||||
const CrossDeviceLink = 'EXDEV'
|
||||
const OperationNotPermitted = 'EPERM'
|
||||
const DeviceIsBusy = 'EBUSY'
|
||||
|
||||
@@ -152,8 +151,14 @@ function isChildOfDir(parent: string, potentialChild: string) {
|
||||
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
|
||||
}
|
||||
|
||||
export async function moveDirContents(srcDir: string, destDir: string): Promise<void[]> {
|
||||
let fileNames = await fs.promises.readdir(srcDir)
|
||||
export async function moveDirContents(srcDir: string, destDir: string): Promise<void> {
|
||||
let fileNames: string[]
|
||||
try {
|
||||
fileNames = await fs.promises.readdir(srcDir)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
await ensureDirectoryExists(destDir)
|
||||
|
||||
if (isChildOfDir(srcDir, destDir)) {
|
||||
@@ -163,10 +168,14 @@ export async function moveDirContents(srcDir: string, destDir: string): Promise<
|
||||
removeFromArray(fileNames, path.basename(destDir))
|
||||
}
|
||||
|
||||
return moveFiles(
|
||||
fileNames.map((fileName) => path.join(srcDir, fileName)),
|
||||
destDir,
|
||||
)
|
||||
try {
|
||||
await moveFiles(
|
||||
fileNames.map((fileName) => path.join(srcDir, fileName)),
|
||||
destDir,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractZip(source: string, dest: string): Promise<void> {
|
||||
@@ -245,14 +254,10 @@ export async function moveFiles(sources: string[], destDir: string): Promise<voi
|
||||
async function moveFile(source: PathLike, destination: PathLike) {
|
||||
try {
|
||||
await fs.promises.rename(source, destination)
|
||||
} catch (error: any) {
|
||||
if (error.code === CrossDeviceLink) {
|
||||
/** Fall back to copying and then deleting. */
|
||||
await fs.promises.copyFile(source, destination)
|
||||
await fs.promises.unlink(source)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
} catch (_error) {
|
||||
/** Fall back to copying and then deleting. */
|
||||
await fs.promises.copyFile(source, destination, fs.constants.COPYFILE_FICLONE_FORCE)
|
||||
await fs.promises.unlink(source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createBackupsManager } from './Backups/BackupsManager'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { FilesBackupManager } from './FileBackups/FileBackupsManager'
|
||||
import { Keychain } from './Keychain/Keychain'
|
||||
import { MediaManager } from './Media/MediaManager'
|
||||
import { MenuManagerInterface } from './Menus/MenuManagerInterface'
|
||||
import { buildContextMenu, createMenuManager } from './Menus/Menus'
|
||||
import { initializePackageManager } from './Packages/PackageManager'
|
||||
@@ -74,6 +75,7 @@ export async function createWindowState({
|
||||
},
|
||||
services.menuManager,
|
||||
services.fileBackupsManager,
|
||||
services.mediaManager,
|
||||
)
|
||||
|
||||
const shouldOpenUrl = (url: string) => url.startsWith('http') || url.startsWith('mailto')
|
||||
@@ -203,6 +205,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
const updateManager = setupUpdates(window, appState, backupsManager)
|
||||
const trayManager = createTrayManager(window, appState.store)
|
||||
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
|
||||
const mediaManager = new MediaManager()
|
||||
|
||||
if (isTesting()) {
|
||||
handleTestMessage(MessageType.SpellCheckerManager, () => spellcheckerManager)
|
||||
@@ -228,6 +231,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
packageManager,
|
||||
searchManager,
|
||||
fileBackupsManager,
|
||||
mediaManager,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,4 +49,6 @@ export interface CrossProcessBridge extends FileBackupsDevice {
|
||||
onInitialDataLoad(): void
|
||||
|
||||
destroyAllData(): void
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import {
|
||||
Environment,
|
||||
FileBackupsMapping,
|
||||
RawKeychainValue,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
|
||||
import { Component } from '../Main/Packages/PackageManagerInterface'
|
||||
@@ -132,6 +135,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
return this.remoteBridge.openFilesBackupsLocation()
|
||||
}
|
||||
|
||||
openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
return this.remoteBridge.openFileBackup(record)
|
||||
}
|
||||
|
||||
async saveFilesBackupsFile(
|
||||
uuid: string,
|
||||
metaFile: string,
|
||||
@@ -144,6 +151,14 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
return this.remoteBridge.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.remoteBridge.getFileBackupReadToken(record)
|
||||
}
|
||||
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.remoteBridge.readNextChunk(token)
|
||||
}
|
||||
|
||||
async performHardReset(): Promise<void> {
|
||||
console.error('performHardReset is not yet implemented')
|
||||
}
|
||||
@@ -151,4 +166,8 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
isDeviceDestroyed(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
return this.remoteBridge.askForMediaAccess(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ declare global {
|
||||
plansUrl: string
|
||||
purchaseUrl: string
|
||||
startApplication: StartApplication
|
||||
zip: any
|
||||
zip: unknown
|
||||
electronMainEvents: any
|
||||
}
|
||||
}
|
||||
@@ -88,15 +88,15 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
/*
|
||||
Title bar events
|
||||
*/
|
||||
document.getElementById('menu-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('menu-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.displayAppMenu()
|
||||
})
|
||||
|
||||
document.getElementById('min-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('min-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.minimizeWindow()
|
||||
})
|
||||
|
||||
document.getElementById('max-btn')!.addEventListener('click', async () => {
|
||||
document.getElementById('max-btn')?.addEventListener('click', async () => {
|
||||
if (remoteBridge.isWindowMaximized()) {
|
||||
remoteBridge.unmaximizeWindow()
|
||||
} else {
|
||||
@@ -104,15 +104,12 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('close-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('close-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.closeWindow()
|
||||
})
|
||||
|
||||
// For Mac inset window
|
||||
const sheet = document.styleSheets[0]
|
||||
if (isMacOS) {
|
||||
sheet.insertRule('#navigation-content { padding-top: 25px !important; }', sheet.cssRules.length)
|
||||
}
|
||||
|
||||
if (isMacOS || useSystemMenuBar) {
|
||||
// !important is important here because #desktop-title-bar has display: flex.
|
||||
@@ -141,7 +138,7 @@ window.electronMainEvents.handlePerformAutomatedBackup(() => {
|
||||
void window.device.downloadBackup()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: any) => {
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: { success: boolean }) => {
|
||||
window.webClient.didFinishBackup(data.success)
|
||||
})
|
||||
|
||||
|
||||
@@ -10,18 +10,14 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.mac-desktop .app-column.selected {
|
||||
padding-top: 18px;
|
||||
.mac-desktop .app-pane {
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.mac-desktop #app.collapsed-notes.collapsed-navigation #editor-column {
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.mac-desktop #app.collapsed-navigation #items-column {
|
||||
padding-top: 18px;
|
||||
.mac-desktop .app-pane-1 {
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.100.8",
|
||||
"version": "3.104.15",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
@@ -104,6 +104,9 @@
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "./build/entitlements.mac.inherit.plist",
|
||||
"entitlementsInherit": "./build/entitlements.mac.inherit.plist",
|
||||
"extendInfo": {
|
||||
"NSCameraUsageDescription": "Standard Notes requires access to your camera to enable the Moments feature."
|
||||
},
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
|
||||
@@ -31,7 +31,7 @@ async function getBlockMapInfo(fileName) {
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const { version } = JSON.parse(await fs.promises.readFile('package.json'))
|
||||
const version = process.argv.slice(2)[0]
|
||||
const zipName = `standard-notes-${version}-mac-x64.zip`
|
||||
const zipPath = `dist/${zipName}`
|
||||
console.log(`Removing ${zipPath}`)
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/docs
|
||||
|
||||
## [0.2.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/docs
|
||||
|
||||
@@ -90,7 +90,7 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
--sn-stylekit-sans-serif-font: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
|
||||
--sn-stylekit-grey-1: #72767e;
|
||||
--sn-stylekit-grey-2: #bbbec4;
|
||||
--sn-stylekit-grey-3: #dfe1e4;
|
||||
@@ -98,7 +98,7 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
--sn-stylekit-grey-4-opacity-variant: #bbbec43d;
|
||||
--sn-stylekit-grey-5: #f4f5f7;
|
||||
--sn-stylekit-grey-6: #e5e5e5;
|
||||
|
||||
|
||||
--sn-stylekit-accessory-tint-color-1: #086dd6;
|
||||
--sn-stylekit-accessory-tint-color-2: #ea6595;
|
||||
--sn-stylekit-accessory-tint-color-3: #ebad00;
|
||||
@@ -108,6 +108,53 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Overrides
|
||||
|
||||
```css
|
||||
--modal-background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
--editor-header-bar-background-color: var(--sn-stylekit-background-color);
|
||||
--editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||
--editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-title-bar-border-bottom-color: var(--sn-stylekit-border-color);
|
||||
--editor-title-input-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-pane-background-color: var(--sn-stylekit-background-color);
|
||||
--editor-pane-editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||
--editor-pane-editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-pane-component-stack-item-background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
--text-selection-color: var(--sn-stylekit-info-contrast-color);
|
||||
--text-selection-background-color: var(--sn-stylekit-info-color);
|
||||
|
||||
--note-preview-progress-color: var(--sn-stylekit-info-color);
|
||||
--note-preview-progress-background-color: var(--sn-stylekit-passive-color-4-opacity-variant);
|
||||
|
||||
--note-preview-selected-progress-color: var(--sn-stylekit-secondary-background-color);
|
||||
--note-preview-selected-progress-background-color: var(--sn-stylekit-passive-color-4-opacity-variant);
|
||||
|
||||
--items-column-background-color: var(--sn-stylekit-background-color);
|
||||
--items-column-items-background-color: var(--sn-stylekit-background-color);
|
||||
--items-column-border-left-color: var(--sn-stylekit-border-color);
|
||||
--items-column-border-right-color: var(--sn-stylekit-border-color);
|
||||
--items-column-search-background-color: var(--sn-stylekit-contrast-background-color);
|
||||
--item-cell-selected-background-color: var(--sn-stylekit-contrast-background-color);
|
||||
--item-cell-selected-border-left-color: var(--sn-stylekit-info-color);
|
||||
|
||||
--navigation-column-background-color: var(--sn-stylekit-secondary-background-color);
|
||||
--navigation-section-title-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
--navigation-item-text-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
--navigation-item-count-color: var(--sn-stylekit-neutral-color);
|
||||
--navigation-item-selected-background-color: rgb(253, 253, 253);
|
||||
|
||||
--preferences-navigation-icon-color: var(--sn-stylekit-neutral-color);
|
||||
--preferences-navigation-selected-background-color: var(--sn-stylekit-info-backdrop-color);
|
||||
|
||||
--dropdown-menu-radio-button-inactive-color: var(--sn-stylekit-passive-color-1);
|
||||
|
||||
--panel-resizer-background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||
--link-element-color: var(--sn-stylekit-info-color);
|
||||
```
|
||||
|
||||
In order to get SN to display a dock icon for your theme (a circle in the lower right corner of the app that allows you to quickly toggle themes), add the following payload into the your ext.json file when [publishing your theme](/extensions/publishing):
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
---
|
||||
id: auth
|
||||
title: Auth API
|
||||
sidebar_label: Auth
|
||||
description: Authentication portion of the Standard Notes API.
|
||||
keywords:
|
||||
- standard notes
|
||||
- docs
|
||||
- notes app
|
||||
- end-to-end encryption
|
||||
- sync
|
||||
image: /img/logo.png
|
||||
hide_title: false
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
### POST /auth
|
||||
|
||||
**Registers a new user**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| created | `string` | Integer timestamp representing the date the client generated the account's encryption keys. Example "1622494310383" |
|
||||
| email | `string` | The account email. |
|
||||
| ephemeral | `boolean` | Whether the initial session created for this account is ephemeral ("Stay signed in" unchecked). |
|
||||
| identifier | `string` | Should equal the account email. |
|
||||
| origination | `string` | Should equal "registration" |
|
||||
| password | `string` | The server password generated by the client from the user's master password. |
|
||||
| pw_nonce | `string` | The nonce generated by the client for the user's encryption key. |
|
||||
| version | `string` | The protocol version the client used to generate the user's encryption key. Latest is "004". |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:457b1f4b-88c2-4328-bf32-fe7dd9431d62:WwKgTEDUoEhxECu6",
|
||||
"refresh_token": "1:457b1f4b-88c2-4328-bf32-fe7dd9431d62:G-qEoZsFtoj~RLL-",
|
||||
"access_expiration": 1627678312000,
|
||||
"refresh_expiration": 1654051238000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "registration",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /auth/sign_in
|
||||
|
||||
**Authenticates a user and returns a session.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| email | `string` | The account email. |
|
||||
| ephemeral | `boolean` | Whether the session created for this account is ephemeral ("Stay signed in" unchecked). |
|
||||
| password | `string` | The server password generated by the client from the user's master password. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:ee03808d-dd05-417c-9c87-d471e49bcc49:Q~-UoIpDhdtkii_t",
|
||||
"refresh_token": "1:ee03808d-dd05-417c-9c87-d471e49bcc49:Jn8UWzeHx2H5nZI5",
|
||||
"access_expiration": 1627678779000,
|
||||
"refresh_expiration": 1654051705000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "registration",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /auth/params
|
||||
|
||||
**Queries the parameters used for key generation for an email address. Queried before signing into an account.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| email | `string` | The account email. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"identifier": "[email protected]",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /auth/change_pw
|
||||
|
||||
**Changes a user's password.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| created | `string` | Integer timestamp representing the date the client generated the account's new encryption keys. Example "1622494310383" |
|
||||
| identifier | `string` | The account email. |
|
||||
| origination | `string` | Should equal "password-change" |
|
||||
| current_password | `string` | The old server password generated by the client from the user's master password. |
|
||||
| new_password | `string` | The new server password generated by the client from the user's master password. |
|
||||
| pw_nonce | `string` | The nonce generated by the client for the user's encryption key. |
|
||||
| version | `string` | The protocol version the client used to generate the user's encryption key. Latest is "004". |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:27d4fd8f-b730-4e0a-afd3-1600fb466aaa:HMgmZwV5k5ePt0vj",
|
||||
"refresh_token": "1:27d4fd8f-b730-4e0a-afd3-1600fb466aaa:wHuTeOB-qWGP3AE3",
|
||||
"access_expiration": 1627679129000,
|
||||
"refresh_expiration": 1654052055000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "password-change",
|
||||
"pw_nonce": "be1974ff6fb1c541aa8c71fd3c66851b6492cf224b661c72daf44e0bef3096bb",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sessions
|
||||
|
||||
### Session Model
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|-----------|---------------------------------------------------|
|
||||
| uuid | `string` | Unique identifier of the session |
|
||||
| user_uuid | `string` | Unique identifier of the user |
|
||||
| user_agent | `string` | The user agent used to create the session |
|
||||
| api_version | `string` | The server API version used to create the session |
|
||||
| access_token | `string` | The access token used to authenticate requests |
|
||||
| refresh_token | `string` | The refresh token used to extend tokens |
|
||||
| access_expiration | `datetime` | The expiration time of the access token |
|
||||
| refresh_expiration | `datetime` | The expiration time of the refresh token |
|
||||
| created_at | `datetime` | Date and time of creation of the session |
|
||||
| updated_at | `datetime` | Last updated date and time of the session |
|
||||
|
||||
- Each `session` includes the API version they were created with. This way we can deny sessions for a given API version if we detect a vulnerability with that version in the future
|
||||
- Sessions are created in the following cases:
|
||||
- When a user signs in
|
||||
- When a user registers a new account
|
||||
|
||||
---
|
||||
|
||||
### Authenticated requests
|
||||
|
||||
The `Authorization` request header field is used by clients to make authenticated request. `Bearer` is the only authentication scheme allowed.
|
||||
|
||||
The client must send the `access` token generated by the session, in the `Authorization` header. For example:
|
||||
|
||||
```
|
||||
GET /sessions HTTP/1.1
|
||||
Host: sync.standardnotes.org
|
||||
Authorization: Bearer <access token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Below is a list of endpoints related to session management:
|
||||
|
||||
| Method | URL | Params | Description | Successful response code |
|
||||
|-----------|--------------------------|---------------------|--------------------------------------------------------|--------------------------|
|
||||
|`POST` | /auth/sign_out | *None* | Terminates the current session | `204` |
|
||||
|`DELETE` | /session | **uuid** | Terminates the specified session by UUID | `204` |
|
||||
|`DELETE` | /sessions | *None* | Terminates all sessions, except the current one | `204` |
|
||||
|`GET` | /sessions | *None* | Lists all sessions active sessions | `200` |
|
||||
|`POST` | /session/token/refresh | **refresh_token** | Obtains new pair of `access_token` and `refresh_token` | `200` |
|
||||
|
||||
A successful request to `GET /sessions` returns the following JSON response:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessions": [
|
||||
{
|
||||
"uuid": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
"user_agent": "<product> / <product-version> <comment>",
|
||||
"api_version": "xxxxyyzz",
|
||||
"current": "<boolean>",
|
||||
"created_at": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Obtaining tokens
|
||||
|
||||
Tokens can be obtained every time the user performs any of the following actions:
|
||||
|
||||
1. When a user signs in
|
||||
1. When a user registers an account
|
||||
1. When the tokens are refreshed
|
||||
|
||||
#### Refreshing tokens
|
||||
|
||||
When an expired `access_token` is provided in the `Authorization` HTTP header, the following JSON response is returned:
|
||||
|
||||
HTTP Status Code: `498 Expired Access Token`
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"tag": "expired-access-token",
|
||||
"message": "The provided access token has expired."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To continue accessing resources, the `access_token` must be refreshed. That is, the current `access_token` is replaced with a new one with an extended expiration date.
|
||||
|
||||
To refresh an `access_token`, a valid `refresh_token` is needed. This `refresh_token` must meet the following requirements:
|
||||
|
||||
- It should belong to the session of the `access_token`
|
||||
- It should not be expired
|
||||
|
||||
Since the `refresh_token` is of single-use, a new `refresh_token` is obtained when the `access_token` is refreshed.
|
||||
|
||||
Refreshing tokens is a process that is transparent to the user, meaning that the client will perform the requests to keep valid tokens without user intervention.
|
||||
|
||||
Here's how to refresh tokens:
|
||||
|
||||
1. Send a `POST` request to `/session/token/refresh`. The body should contain a JSON paylaod with the `refresh_token`:
|
||||
```
|
||||
POST /session/token/refresh HTTP/1.1
|
||||
Host: sync.standardnotes.org
|
||||
Authorization: Bearer <access token>
|
||||
|
||||
{
|
||||
"refresh_token": "R_xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
1. The `refresh_token` is validated on the server. Depending of the circumstances, there should be two outcomes:
|
||||
1. The provided `refresh_token` is valid. If so, a new pair of tokens is generated and the following JSON response is returned:
|
||||
```json
|
||||
{
|
||||
"token": "xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
"session": {
|
||||
"refresh_expiration": 1583020800,
|
||||
"refresh_token": "xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
}
|
||||
}
|
||||
```
|
||||
1. The provided `refresh_token` is expired. If so, the following JSON response is returned:
|
||||
|
||||
HTTP Status Code: `400 Bad Request`
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"tag": "expired-refresh-token",
|
||||
"message": "The refresh token has expired."
|
||||
}
|
||||
}
|
||||
```
|
||||
User must start a new session by re-entering their credentials.
|
||||
|
||||
### Expiration
|
||||
|
||||
#### Sessions
|
||||
|
||||
Sessions should be terminated after a period of inactivity. This is for best security practices.
|
||||
|
||||
`Long-lived sessions` are a good choice for our use case, because it can build a better user experience than expiring sessions for a short idle-timeout.
|
||||
|
||||
- A `session` that remains inactive for `1 year` will be terminated, along with all `tokens`
|
||||
|
||||
#### Tokens
|
||||
|
||||
| Name | Type | Expiration |
|
||||
|-----------|-------------|---------------|
|
||||
| `access` | Short-lived | `60 days` |
|
||||
| `refresh` | Long-lived | `1 year` |
|
||||
|
||||
- A `refresh` token is of single-use, while an `access` token can be used as long as it is not expired
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
id: sync
|
||||
title: Sync API
|
||||
sidebar_label: Sync
|
||||
description: Specification for the Standard Notes Sync API.
|
||||
keywords:
|
||||
- standard notes
|
||||
- docs
|
||||
- notes app
|
||||
- end-to-end encryption
|
||||
- sync
|
||||
image: /img/logo.png
|
||||
hide_title: false
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
This document outlines the client-server communication of the Standard Notes ecosystem.
|
||||
|
||||
## Item
|
||||
|
||||
An `Item` is largely the only model that both clients and server are concerned wtih. The `content` field stores an encrypted object that can be anything the client needs to operate.
|
||||
|
||||
Relationships between items are handled by the client, and not the server, and are stored encrypted in the `content` field as `references`.
|
||||
|
||||
Items have the following properties:
|
||||
|
||||
| name | type | description |
|
||||
| ------------ | ----------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| uuid | `string` (or uuid for some databases) | The unique identifier for this item. |
|
||||
| content | `text` | An encrypted string generated by the client. |
|
||||
| content_type | `string` | The type of data contained in the `content` field. (i.e Note, Tag, SN|Component, etc.) |
|
||||
| enc_item_key | `text` | The key used to encrypt `content`, itself also encrypted. |
|
||||
| deleted | `boolean` | Whether the model has been deleted. |
|
||||
| created_at_timestamp | `integer` | Timestamp representing when the item was created, with microsecond precision. |
|
||||
| updated_at_timestamp | `integer` | Timestamp representing when the item was updated _by the server_, with microsecond precision. |
|
||||
|
||||
## Content
|
||||
|
||||
Data generated by the client is stored in the `content` field of the `Item`. An item's `content` is "sticky" and can have any property the client chooses, as well as the following:
|
||||
|
||||
| name | type | description |
|
||||
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| appData | `dictionary` | A domain based dictionary whose content must be preserved between platforms. `appData[domain]` contains data the client stores in the item. |
|
||||
| references | `array` | A metadata array of other `Item` uuids this item is related to and their respective `content_type`. See sample below. |
|
||||
|
||||
`references` array structure:
|
||||
|
||||
```JSON
|
||||
[
|
||||
{uuid: xxxx, content_type: "Tag"},
|
||||
{uuid: xxxxx, content_type: "Tag"},
|
||||
]
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
General:
|
||||
|
||||
1. All requests after initial authentication should be authenticated with a session token in the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <access token>
|
||||
|
||||
```
|
||||
|
||||
### POST items/sync
|
||||
|
||||
| | | |
|
||||
|-------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115". |
|
||||
| compute_integrity | `boolean` | Should be false in most cases. Pass true if you plan to read the `integrity_hash` result (integrity can be optionally checked once on app launch). |
|
||||
| items | `array` | An array of Items. |
|
||||
| limit | `integer` | The maximum number of items to return per request. Official clients use 150. |
|
||||
| sync_token | `string` | An opaque token returned by the server from a previous sync request. This lets the server know to pull in only new changes. |
|
||||
| cursor_token | `string` | An opaque pagination token returned by the server from a previous multi-page sync request. Send this value back only if the server returned this value from a previous request. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"retrieved_items": Array<Item>,
|
||||
"saved_items": Array<Item>,
|
||||
"conflicts": Array<Conflict>,
|
||||
"sync_token": string,
|
||||
"cursor_token": string?,
|
||||
"integrity_hash": string?,
|
||||
}
|
||||
```
|
||||
|
||||
| | |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| retrieved_items | Items that are new to the client or have been modified since last sync and should be merged with local values or created locally. |
|
||||
| saved_items | Items which have been successfully saved. Clients should merge *only* the resulting `updated_at_timestamp` with local values. Do not update local value of `content` from remote `saved_items` value. |
|
||||
| conflicts | Items which have not been saved by the server and returned to client. |
|
||||
| sync_token | An opaque token which should be sent back to server in subsequent sync request. |
|
||||
| cursor_token | An opaque token which should be sent back to server in subsequent sync request (if present). |
|
||||
|
||||
#### Conflicts
|
||||
|
||||
```typescript
|
||||
type Conflict = {
|
||||
server_item?: Item
|
||||
unsaved_item?: Item
|
||||
type: 'sync_conflict' | 'uuid_conflict'
|
||||
}
|
||||
```
|
||||
|
||||
A `sync_conflict` occurs when the item the client is attempting to save has a newer change on the server. The server is able to determine this by reading the `updated_at_timestamp` value from both the incoming payload and the server payload. If the incoming payload's `updated_at_timestamp` != to the server's `updated_at_timestamp` for that item, the save is rejected and returned to client as a `sync_conflict`. These types of conflicts are not uncommon.
|
||||
|
||||
To resolve a `sync_conflict`, the client must choose a winner between server_item and unsaved_item then re-sync. The winning item should have its updated_at_timestamp value set to the server's value.
|
||||
|
||||
A `uuid_conflict` occurs when the UUID the client has choosen for an item for X user already belongs to an item for Y user. This should mostly only occur when a user registers a new account and imports a data backup from another account.
|
||||
|
||||
To resolve a `uuid_conflict`, change the UUID of the item attempting to be saved to a new UUID, then re-sync.
|
||||
|
||||
#### Deletion
|
||||
|
||||
To delete an item permanently, set `deleted` to `true` on the `Item` and sync. When receiving an item that is `deleted`, remove it from the local database immediately.
|
||||
@@ -23,8 +23,6 @@ This documentation explains how to get started with using Standard Notes, publis
|
||||
- [Self-hosting a Syncing Server](/self-hosting/getting-started)
|
||||
- [Developing Extensions](/extensions/intro)
|
||||
- [Client Encryption API](/specification/encryption)
|
||||
- [Sync API](/specification/sync)
|
||||
- [Auth API](/specification/auth)
|
||||
|
||||
## Staying informed
|
||||
|
||||
|
||||
@@ -43,11 +43,6 @@ module.exports = {
|
||||
label: 'Client Encryption API',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
to: '/specification/sync',
|
||||
label: 'Server API',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
to: '/extensions/intro',
|
||||
label: 'Build an Extension',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/docs",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -14,11 +14,7 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
{
|
||||
API: [
|
||||
'specification/encryption',
|
||||
'specification/sync',
|
||||
'specification/auth',
|
||||
],
|
||||
API: ['specification/encryption'],
|
||||
},
|
||||
{
|
||||
Extensions: [
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.19.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.14",
|
||||
"version": "1.19.26",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.56.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* add Super note type to list of note types ([#2086](https://github.com/standardnotes/app/issues/2086)) ([caf2c4a](https://github.com/standardnotes/app/commit/caf2c4a87646ab4d1aa7007c39547fd3b16c0c6f))
|
||||
|
||||
## [1.55.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.55.3",
|
||||
"version": "1.56.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -51,4 +51,4 @@ export enum FeatureIdentifier {
|
||||
*/
|
||||
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
|
||||
|
||||
export const ExperimentalFeatures = [FeatureIdentifier.SuperEditor]
|
||||
export const ExperimentalFeatures = []
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ClientFeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
|
||||
export function clientFeatures(): ClientFeatureDescription[] {
|
||||
return [
|
||||
@@ -12,6 +12,15 @@ export function clientFeatures(): ClientFeatureDescription[] {
|
||||
permission_name: PermissionName.TagNesting,
|
||||
description: 'Organize your tags into folders.',
|
||||
},
|
||||
{
|
||||
name: 'Super Notes',
|
||||
identifier: FeatureIdentifier.SuperEditor,
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
permission_name: PermissionName.SuperEditor,
|
||||
description:
|
||||
'Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
name: 'Smart Filters',
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import { FeatureIdentifier } from './../Feature/FeatureIdentifier'
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
|
||||
export function experimentalFeatures(): FeatureDescription[] {
|
||||
const superEditor: FeatureDescription = {
|
||||
name: 'Super Notes',
|
||||
identifier: FeatureIdentifier.SuperEditor,
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
permission_name: PermissionName.SuperEditor,
|
||||
description:
|
||||
'A new way to edit notes. Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
}
|
||||
|
||||
return [superEditor]
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.27.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.27.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** Fixes issue where file upload doesn't work with immediate biometrics ([#2083](https://github.com/standardnotes/app/issues/2083)) ([6cacdd8](https://github.com/standardnotes/app/commit/6cacdd8c5882ac562c73a706aa4237c53a049f5c))
|
||||
|
||||
## [1.27.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [1.26.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
|
||||
|
||||
## [1.25.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.25.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.25.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.25.14",
|
||||
"version": "1.27.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"description": "Web filepicker for Standard Notes projects",
|
||||
"main": "dist/index.js",
|
||||
"main": "./src/index.ts",
|
||||
"author": "Standard Notes",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"types": "./src/index.ts",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
"prestart": "yarn clean",
|
||||
"start": "tsc -p tsconfig.json --watch",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"build": "echo 'Empty build script required for yarn topological install'",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"test": "jest"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ByteChunker, OnChunkCallback } from '@standardnotes/files'
|
||||
import { ByteChunker, OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
import { FileSelectionResponse } from '../types'
|
||||
import { readFile as utilsReadFile } from '../utils'
|
||||
import { FileReaderInterface } from '../Interface/FileReader'
|
||||
@@ -18,16 +18,31 @@ function maximumFileSize(): number {
|
||||
return 50 * 1_000_000
|
||||
}
|
||||
|
||||
const FileInputId = 'classic-reader-file-input'
|
||||
function createFileInputOrReturnExisting(): HTMLInputElement {
|
||||
let fileInput = document.getElementById(FileInputId) as HTMLInputElement
|
||||
if (fileInput) {
|
||||
return fileInput
|
||||
}
|
||||
|
||||
fileInput = document.createElement('input')
|
||||
fileInput.id = FileInputId
|
||||
fileInput.type = 'file'
|
||||
fileInput.className = 'absolute top-0 left-0 -z-50 h-px w-px opacity-0'
|
||||
fileInput.multiple = true
|
||||
document.body.appendChild(fileInput)
|
||||
|
||||
return fileInput
|
||||
}
|
||||
|
||||
function selectFiles(): Promise<File[]> {
|
||||
const input = document.createElement('input') as HTMLInputElement
|
||||
input.type = 'file'
|
||||
input.multiple = true
|
||||
const input = createFileInputOrReturnExisting()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
input.onchange = async (event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const files = []
|
||||
for (const file of target.files as FileList) {
|
||||
for (const file of Array.from(target.files as FileList)) {
|
||||
files.push(file)
|
||||
}
|
||||
resolve(files)
|
||||
@@ -39,7 +54,7 @@ function selectFiles(): Promise<File[]> {
|
||||
async function readFile(
|
||||
file: File,
|
||||
minimumChunkSize: number,
|
||||
onChunk: OnChunkCallback,
|
||||
onChunk: OnChunkCallbackNoProgress,
|
||||
): Promise<FileSelectionResponse> {
|
||||
const buffer = await utilsReadFile(file)
|
||||
const chunker = new ByteChunker(minimumChunkSize, onChunk)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { OnChunkCallback } from '@standardnotes/files'
|
||||
import { OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
|
||||
import { FileSelectionResponse } from '../types'
|
||||
|
||||
export interface FileReaderInterface {
|
||||
selectFiles(): Promise<File[]>
|
||||
|
||||
readFile(file: File, minimumChunkSize: number, onChunk: OnChunkCallback): Promise<FileSelectionResponse>
|
||||
readFile(file: File, minimumChunkSize: number, onChunk: OnChunkCallbackNoProgress): Promise<FileSelectionResponse>
|
||||
|
||||
available(): boolean
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ByteChunker, OnChunkCallback } from '@standardnotes/files'
|
||||
import { ByteChunker, OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
import { FileReaderInterface } from './../Interface/FileReader'
|
||||
import { FileSelectionResponse } from '../types'
|
||||
|
||||
@@ -39,7 +39,7 @@ async function selectFiles(): Promise<File[]> {
|
||||
async function readFile(
|
||||
file: File,
|
||||
minimumChunkSize: number,
|
||||
onChunk: OnChunkCallback,
|
||||
onChunk: OnChunkCallbackNoProgress,
|
||||
): Promise<FileSelectionResponse> {
|
||||
const byteChunker = new ByteChunker(minimumChunkSize, onChunk)
|
||||
const stream = file.stream() as unknown as ReadableStream
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"extends": "../../UILib.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
|
||||
@@ -3,6 +3,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [1.13.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
|
||||
|
||||
## [1.12.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.12.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.12.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.12.14",
|
||||
"version": "1.14.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"description": "Client-side files library",
|
||||
"main": "dist/index.js",
|
||||
"main": "./src/index.ts",
|
||||
"author": "Standard Notes",
|
||||
"types": "dist/index.d.ts",
|
||||
"types": "./src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
"prestart": "yarn clean",
|
||||
"start": "tsc -p tsconfig.json --watch",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"build": "echo 'Empty build script required for yarn topological install'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.3",
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('byte chunker', () => {
|
||||
it('should hold back small chunks until minimum size is met', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), false)
|
||||
@@ -25,9 +25,9 @@ describe('byte chunker', () => {
|
||||
it('should send back big chunks immediately', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(150), false)
|
||||
@@ -42,9 +42,9 @@ describe('byte chunker', () => {
|
||||
it('last chunk should be popped regardless of size', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), false)
|
||||
@@ -57,9 +57,9 @@ describe('byte chunker', () => {
|
||||
it('single chunk should be popped immediately', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), true)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { OnChunkCallback } from './OnChunkCallback'
|
||||
import { OnChunkCallbackNoProgress } from './OnChunkCallback'
|
||||
|
||||
export class ByteChunker {
|
||||
public loggingEnabled = false
|
||||
private bytes = new Uint8Array()
|
||||
private index = 1
|
||||
|
||||
constructor(private minimumChunkSize: number, private onChunk: OnChunkCallback) {}
|
||||
constructor(private minimumChunkSize: number, private onChunk: OnChunkCallbackNoProgress) {}
|
||||
|
||||
private log(...args: any[]): void {
|
||||
if (!this.loggingEnabled) {
|
||||
@@ -27,9 +27,13 @@ export class ByteChunker {
|
||||
|
||||
private async popBytes(isLast: boolean): Promise<void> {
|
||||
const maxIndex = Math.max(this.minimumChunkSize, this.bytes.length)
|
||||
|
||||
const chunk = this.bytes.slice(0, maxIndex)
|
||||
|
||||
this.bytes = new Uint8Array([...this.bytes.slice(maxIndex)])
|
||||
|
||||
this.log(`Chunker popping ${chunk.length}, total size in queue ${this.bytes.length}`)
|
||||
await this.onChunk(chunk, this.index++, isLast)
|
||||
|
||||
await this.onChunk({ data: chunk, index: this.index++, isLast })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
export type OnChunkCallback = (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
|
||||
export type OnChunkCallback = (chunk: {
|
||||
data: Uint8Array
|
||||
index: number
|
||||
isLast: boolean
|
||||
progress: FileDownloadProgress
|
||||
}) => Promise<void>
|
||||
|
||||
export type OnChunkCallbackNoProgress = (chunk: { data: Uint8Array; index: number; isLast: boolean }) => Promise<void>
|
||||
|
||||
@@ -10,9 +10,30 @@ describe('ordered byte chunker', () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numCallbacks = 0
|
||||
|
||||
const chunker = new OrderedByteChunker(chunkSizes, async (bytes) => {
|
||||
const chunker = new OrderedByteChunker(chunkSizes, 'network', async (chunk) => {
|
||||
numCallbacks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(30))
|
||||
|
||||
expect(numCallbacks).toEqual(3)
|
||||
expect(receivedBytes.length).toEqual(30)
|
||||
})
|
||||
|
||||
it('should correctly report progress', async () => {
|
||||
const chunkSizes = [10, 10, 10]
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numCallbacks = 0
|
||||
|
||||
const chunker = new OrderedByteChunker(chunkSizes, 'network', async (chunk) => {
|
||||
numCallbacks++
|
||||
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
|
||||
expect(chunk.progress.encryptedBytesDownloaded).toEqual(receivedBytes.length)
|
||||
|
||||
expect(chunk.progress.percentComplete).toEqual((numCallbacks / chunkSizes.length) * 100.0)
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(30))
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
import { OnChunkCallback } from './OnChunkCallback'
|
||||
|
||||
export class OrderedByteChunker {
|
||||
private bytes = new Uint8Array()
|
||||
private index = 1
|
||||
private remainingChunks: number[] = []
|
||||
private fileSize: number
|
||||
|
||||
constructor(
|
||||
private chunkSizes: number[],
|
||||
private onChunk: (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>,
|
||||
private source: FileDownloadProgress['source'],
|
||||
private onChunk: OnChunkCallback,
|
||||
) {
|
||||
this.remainingChunks = chunkSizes.slice()
|
||||
|
||||
this.fileSize = chunkSizes.reduce((acc, size) => acc + size, 0)
|
||||
}
|
||||
|
||||
private get bytesPopped(): number {
|
||||
return this.fileSize - this.bytesRemaining
|
||||
}
|
||||
|
||||
private get bytesRemaining(): number {
|
||||
return this.remainingChunks.reduce((acc, size) => acc + size, 0)
|
||||
}
|
||||
|
||||
private needsPop(): boolean {
|
||||
@@ -31,7 +46,18 @@ export class OrderedByteChunker {
|
||||
|
||||
this.remainingChunks.shift()
|
||||
|
||||
await this.onChunk(chunk, this.index++, this.index === this.chunkSizes.length - 1)
|
||||
await this.onChunk({
|
||||
data: chunk,
|
||||
index: this.index++,
|
||||
isLast: this.index === this.chunkSizes.length - 1,
|
||||
progress: {
|
||||
encryptedFileSize: this.fileSize,
|
||||
encryptedBytesDownloaded: this.bytesPopped,
|
||||
encryptedBytesRemaining: this.bytesRemaining,
|
||||
percentComplete: (this.bytesPopped / this.fileSize) * 100.0,
|
||||
source: this.source,
|
||||
},
|
||||
})
|
||||
|
||||
if (this.needsPop()) {
|
||||
await this.popBytes()
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { FileBackupsMapping } from './FileBackupsMapping'
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
import { FileBackupRecord, FileBackupsMapping } from './FileBackupsMapping'
|
||||
|
||||
export type FileBackupReadToken = string
|
||||
export type FileBackupReadChunkResponse = { chunk: Uint8Array; isLast: boolean; progress: FileDownloadProgress }
|
||||
|
||||
export interface FileBackupsDevice {
|
||||
getFilesBackupsMappingFile(): Promise<FileBackupsMapping>
|
||||
@@ -12,10 +16,13 @@ export interface FileBackupsDevice {
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'>
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken>
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
changeFilesBackupsLocation(): Promise<string | undefined>
|
||||
getFilesBackupsLocation(): Promise<string>
|
||||
openFilesBackupsLocation(): Promise<void>
|
||||
openFileBackup(record: FileBackupRecord): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { FileBackupsConstantsV1 } from './FileBackupsConstantsV1'
|
||||
|
||||
export type FileBackupRecord = {
|
||||
backedUpOn: Date
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
|
||||
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
|
||||
version: typeof FileBackupsConstantsV1.Version
|
||||
}
|
||||
|
||||
export interface FileBackupsMapping {
|
||||
version: typeof FileBackupsConstantsV1.Version
|
||||
files: Record<
|
||||
Uuid,
|
||||
{
|
||||
backedUpOn: Date
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
|
||||
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
|
||||
version: typeof FileBackupsConstantsV1.Version
|
||||
}
|
||||
>
|
||||
files: Record<Uuid, FileBackupRecord>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { logWithColor } from '@standardnotes/utils'
|
||||
|
||||
declare const process: {
|
||||
env: {
|
||||
NODE_ENV: string | null | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'
|
||||
|
||||
export enum LoggingDomain {
|
||||
FilesPackage,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.FilesPackage]: false,
|
||||
}
|
||||
|
||||
const DomainColor: Record<LoggingDomain, string> = {
|
||||
[LoggingDomain.FilesPackage]: 'green',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDev || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
logWithColor(LoggingDomain[domain], DomainColor[domain], ...args)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
|
||||
import { FileBackupRecord } from '../Device/FileBackupsMapping'
|
||||
|
||||
export interface BackupServiceInterface {
|
||||
getFileBackupInfo(file: { uuid: string }): Promise<FileBackupRecord | undefined>
|
||||
readEncryptedFileFromBackup(uuid: string, onChunk: OnChunkCallback): Promise<'success' | 'failed' | 'aborted'>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
changeFilesBackupsLocation(): Promise<string | undefined>
|
||||
getFilesBackupsLocation(): Promise<string>
|
||||
openFilesBackupsLocation(): Promise<void>
|
||||
openFileBackup(record: FileBackupRecord): Promise<void>
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export interface FilesClientInterface {
|
||||
|
||||
downloadFile(
|
||||
file: FileItem,
|
||||
onDecryptedBytes: (bytes: Uint8Array, progress: FileDownloadProgress | undefined) => Promise<void>,
|
||||
onDecryptedBytes: (bytes: Uint8Array, progress: FileDownloadProgress) => Promise<void>,
|
||||
): Promise<ClientDisplayableError | undefined>
|
||||
|
||||
deleteFile(file: FileItem): Promise<ClientDisplayableError | undefined>
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import { FileSystemApi } from '../Api/FileSystemApi'
|
||||
import { FileHandleRead } from '../Api/FileHandleRead'
|
||||
import { OrderedByteChunker } from '../Chunker/OrderedByteChunker'
|
||||
|
||||
export async function readAndDecryptBackupFile(
|
||||
export async function readAndDecryptBackupFileUsingFileSystemAPI(
|
||||
fileHandle: FileHandleRead,
|
||||
file: {
|
||||
encryptionHeader: FileContent['encryptionHeader']
|
||||
@@ -19,8 +19,8 @@ export async function readAndDecryptBackupFile(
|
||||
): Promise<'aborted' | 'failed' | 'success'> {
|
||||
const decryptor = new FileDecryptor(file, crypto)
|
||||
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, async (chunk: Uint8Array) => {
|
||||
const decryptResult = decryptor.decryptBytes(chunk)
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, 'local', async (chunk) => {
|
||||
const decryptResult = decryptor.decryptBytes(chunk.data)
|
||||
|
||||
if (!decryptResult) {
|
||||
return
|
||||
@@ -0,0 +1,52 @@
|
||||
import { FileContent } from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { FileDecryptor } from '../UseCase/FileDecryptor'
|
||||
import { OrderedByteChunker } from '../Chunker/OrderedByteChunker'
|
||||
import { BackupServiceInterface } from './BackupServiceInterface'
|
||||
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
|
||||
import { log, LoggingDomain } from '../Logging'
|
||||
|
||||
export async function readAndDecryptBackupFileUsingBackupService(
|
||||
file: {
|
||||
uuid: string
|
||||
encryptionHeader: FileContent['encryptionHeader']
|
||||
remoteIdentifier: FileContent['remoteIdentifier']
|
||||
encryptedChunkSizes: FileContent['encryptedChunkSizes']
|
||||
key: FileContent['key']
|
||||
},
|
||||
backupService: BackupServiceInterface,
|
||||
crypto: PureCryptoInterface,
|
||||
onDecryptedBytes: OnChunkCallback,
|
||||
): Promise<'aborted' | 'failed' | 'success'> {
|
||||
log(
|
||||
LoggingDomain.FilesPackage,
|
||||
'Reading and decrypting backup file',
|
||||
file.uuid,
|
||||
'chunk sizes',
|
||||
file.encryptedChunkSizes,
|
||||
)
|
||||
|
||||
const decryptor = new FileDecryptor(file, crypto)
|
||||
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, 'local', async (chunk) => {
|
||||
log(LoggingDomain.FilesPackage, 'OrderedByteChunker did pop bytes', chunk.data.length, chunk.progress)
|
||||
|
||||
const decryptResult = decryptor.decryptBytes(chunk.data)
|
||||
|
||||
if (!decryptResult) {
|
||||
return
|
||||
}
|
||||
|
||||
await onDecryptedBytes({ ...chunk, data: decryptResult.decryptedBytes })
|
||||
})
|
||||
|
||||
const readResult = await backupService.readEncryptedFileFromBackup(file.uuid, async (chunk) => {
|
||||
log(LoggingDomain.FilesPackage, 'Got file chunk from backup service', chunk.data.length, chunk.progress)
|
||||
|
||||
await byteChunker.addBytes(chunk.data)
|
||||
})
|
||||
|
||||
log(LoggingDomain.FilesPackage, 'Finished reading and decrypting backup file', file.uuid)
|
||||
|
||||
return readResult
|
||||
}
|
||||
@@ -3,4 +3,24 @@ export type FileDownloadProgress = {
|
||||
encryptedBytesDownloaded: number
|
||||
encryptedBytesRemaining: number
|
||||
percentComplete: number
|
||||
source: 'network' | 'local' | 'memcache'
|
||||
}
|
||||
|
||||
export function fileProgressToHumanReadableString(
|
||||
progress: FileDownloadProgress,
|
||||
fileName: string,
|
||||
options: { showPercent: boolean },
|
||||
): string {
|
||||
const progressPercent = Math.floor(progress.percentComplete)
|
||||
|
||||
const sourceString =
|
||||
progress.source === 'network' ? '' : progress.source === 'memcache' ? 'from cache' : 'from backup'
|
||||
|
||||
let result = `Downloading file ${sourceString} "${fileName}"`
|
||||
|
||||
if (options.showPercent) {
|
||||
result += ` (${progressPercent}%)`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export class FileDownloader {
|
||||
encryptedBytesDownloaded: this.totalBytesDownloaded,
|
||||
encryptedBytesRemaining: encryptedSize - this.totalBytesDownloaded,
|
||||
percentComplete: (this.totalBytesDownloaded / encryptedSize) * 100.0,
|
||||
source: 'network',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ export * from './Device/FileBackupMetadataFile'
|
||||
export * from './Device/FileBackupsConstantsV1'
|
||||
export * from './Device/FileBackupsDevice'
|
||||
export * from './Device/FileBackupsMapping'
|
||||
export * from './Service/BackupServiceInterface'
|
||||
export * from './Service/FilesClientInterface'
|
||||
export * from './Service/ReadAndDecryptBackupFile'
|
||||
export * from './Service/ReadAndDecryptBackupFileFileSystemAPI'
|
||||
export * from './Service/ReadAndDecryptBackupFileUsingBackupService'
|
||||
export * from './Operations/DownloadAndDecrypt'
|
||||
export * from './Operations/EncryptAndUpload'
|
||||
export * from './UseCase/FileDecryptor'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"extends": "../../UILib.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add new line after embedding files ([7f6c53d](https://github.com/standardnotes/app/commit/7f6c53dfcbc8952ca9eee76b9c4af70fea3c1bf1))
|
||||
|
||||
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
### Features
|
||||
|
||||
* ability to drag super list items; secure password generation blocks ([#2039](https://github.com/standardnotes/app/issues/2039)) ([c39c72d](https://github.com/standardnotes/app/commit/c39c72da7a4fb85f4da9aa4e6f8e9f7ba4486a94))
|
||||
|
||||
# [1.5.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **web:** enable block drag'n'drop in super editor ([#2029](https://github.com/standardnotes/app/issues/2029)) ([dab4f67](https://github.com/standardnotes/app/commit/dab4f678f04aee2c7dfbe7f416e7fc34e8d870cf))
|
||||
|
||||
## [1.4.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/icons
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/icons",
|
||||
"version": "1.4.10",
|
||||
"version": "1.6.1",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="9px" height="14px" viewBox="0 0 9 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-2" fill="currentColor" fill-rule="nonzero">
|
||||
<path d="M1.6666,3.33333 C1.2246,3.33333 0.8007,3.15774 0.4881,2.84518 C0.1755,2.53262 0,2.1087 0,1.66667 C0,1.22464 0.1755,0.80072 0.4881,0.48816 C0.8007,0.1756 1.2246,0 1.6666,0 C2.1086,0 2.5326,0.1756 2.8451,0.48816 C3.1577,0.80072 3.3333,1.22464 3.3333,1.66667 C3.3333,2.1087 3.1577,2.53262 2.8451,2.84518 C2.5326,3.15774 2.1086,3.33333 1.6666,3.33333 Z M1.6666,8.33333 C1.2246,8.33333 0.8007,8.15773 0.4881,7.84513 C0.1755,7.53263 0,7.10873 0,6.66663 C0,6.22463 0.1755,5.80073 0.4881,5.48813 C0.8007,5.17563 1.2246,5.00003 1.6666,5.00003 C2.1086,5.00003 2.5326,5.17563 2.8451,5.48813 C3.1577,5.80073 3.3333,6.22463 3.3333,6.66663 C3.3333,7.10873 3.1577,7.53263 2.8451,7.84513 C2.5326,8.15773 2.1086,8.33333 1.6666,8.33333 Z M1.6666,13.33333 C1.2246,13.33333 0.8007,13.15773 0.4881,12.84513 C0.1755,12.53263 0,12.10873 0,11.66663 C0,11.22463 0.1755,10.80073 0.4881,10.48813 C0.8007,10.17563 1.2246,10.00003 1.6666,10.00003 C2.1086,10.00003 2.5326,10.17563 2.8451,10.48813 C3.1577,10.80073 3.3333,11.22463 3.3333,11.66663 C3.3333,12.10873 3.1577,12.53263 2.8451,12.84513 C2.5326,13.15773 2.1086,13.33333 1.6666,13.33333 L1.6666,13.33333 Z M6.6666,3.33333 C6.2246,3.33333 5.8007,3.15774 5.4881,2.84518 C5.1755,2.53262 5,2.1087 5,1.66667 C5,1.22464 5.1755,0.80072 5.4881,0.48816 C5.8007,0.1756 6.2246,0 6.6666,0 C7.1086,0 7.5326,0.1756 7.8451,0.48816 C8.1577,0.80072 8.3333,1.22464 8.3333,1.66667 C8.3333,2.1087 8.1577,2.53262 7.8451,2.84518 C7.5326,3.15774 7.1086,3.33333 6.6666,3.33333 Z M6.6666,8.33333 C6.2246,8.33333 5.8007,8.15773 5.4881,7.84513 C5.1755,7.53263 5,7.10873 5,6.66663 C5,6.22463 5.1755,5.80073 5.4881,5.48813 C5.8007,5.17563 6.2246,5.00003 6.6666,5.00003 C7.1086,5.00003 7.5326,5.17563 7.8451,5.48813 C8.1577,5.80073 8.3333,6.22463 8.3333,6.66663 C8.3333,7.10873 8.1577,7.53263 7.8451,7.84513 C7.5326,8.15773 7.1086,8.33333 6.6666,8.33333 Z M6.6666,13.33333 C6.2246,13.33333 5.8007,13.15773 5.4881,12.84513 C5.1755,12.53263 5,12.10873 5,11.66663 C5,11.22463 5.1755,10.80073 5.4881,10.48813 C5.8007,10.17563 6.2246,10.00003 6.6666,10.00003 C7.1086,10.00003 7.5326,10.17563 7.8451,10.48813 C8.1577,10.80073 8.3333,11.22463 8.3333,11.66663 C8.3333,12.10873 8.1577,12.53263 7.8451,12.84513 C7.5326,13.15773 7.1086,13.33333 6.6666,13.33333 L6.6666,13.33333 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.99992 15C8.67384 15 7.40207 14.4732 6.46438 13.5355C5.5267 12.5978 4.99992 11.3261 4.99992 9.99998C4.99992 9.16665 5.20825 8.35831 5.58325 7.66665L4.36659 6.44998C3.71659 7.47498 3.33325 8.69165 3.33325 9.99998C3.33325 11.7681 4.03563 13.4638 5.28587 14.714C6.53612 15.9643 8.23181 16.6666 9.99992 16.6666V19.1666L13.3333 15.8333L9.99992 12.5V15ZM9.99992 3.33331V0.833313L6.66658 4.16665L9.99992 7.49998V4.99998C11.326 4.99998 12.5978 5.52676 13.5355 6.46445C14.4731 7.40213 14.9999 8.6739 14.9999 9.99998C14.9999 10.8333 14.7916 11.6416 14.4166 12.3333L15.6333 13.55C16.2833 12.525 16.6666 11.3083 16.6666 9.99998C16.6666 8.23187 15.9642 6.53618 14.714 5.28593C13.4637 4.03569 11.768 3.33331 9.99992 3.33331Z" />
|
||||
<path d="M9.99999 15C11.3261 15 12.5978 14.4732 13.5355 13.5355C14.4732 12.5979 15 11.3261 15 10C15 9.16666 14.7917 8.35833 14.4167 7.66667L15.6333 6.45C16.2833 7.475 16.6667 8.69166 16.6667 10C16.6667 11.7681 15.9643 13.4638 14.714 14.714C13.4638 15.9643 11.7681 16.6667 9.99999 16.6667V19.1667L6.66666 15.8333L9.99999 12.5V15ZM9.99999 3.33333V0.833333L13.3333 4.16667L9.99999 7.5V5C8.67391 5 7.40214 5.52678 6.46446 6.46447C5.52677 7.40215 4.99999 8.67392 4.99999 10C4.99999 10.8333 5.20832 11.6417 5.58332 12.3333L4.36666 13.55C3.71666 12.525 3.33332 11.3083 3.33332 10C3.33332 8.23189 4.0357 6.5362 5.28594 5.28595C6.53619 4.03571 8.23188 3.33333 9.99999 3.33333Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 756 B |
@@ -21,6 +21,7 @@ import AttachmentFileIcon from './ic-attachment-file.svg'
|
||||
import AuthenticatorIcon from './ic-authenticator.svg'
|
||||
import AuthenticatorVariantIcon from './ic-authenticator-variant.svg'
|
||||
import BackIosIcon from './ic-back-ios.svg'
|
||||
import BlockIcon from './ic-block.svg'
|
||||
import BlueDotIcon from './blue-dot.svg'
|
||||
import BoldIcon from './ic-bold.svg'
|
||||
import BoxFilledIcon from './ic-box-filled.svg'
|
||||
@@ -224,6 +225,7 @@ export {
|
||||
AuthenticatorIcon,
|
||||
AuthenticatorVariantIcon,
|
||||
BackIosIcon,
|
||||
BlockIcon,
|
||||
BlueDotIcon,
|
||||
BoldIcon,
|
||||
BoxFilledIcon,
|
||||
|
||||
@@ -27,6 +27,7 @@ import TypeSubscript from './type-subscript.svg'
|
||||
import TypeSuperscript from './type-superscript.svg'
|
||||
import TypeUnderline from './type-underline.svg'
|
||||
import LexicalPencilFill from './pencil-fill.svg'
|
||||
import LexicalDraggableBlockMenu from './draggable-block-menu.svg'
|
||||
|
||||
export {
|
||||
LexicalCaretRightFill,
|
||||
@@ -58,4 +59,5 @@ export {
|
||||
TypeSuperscript,
|
||||
TypeUnderline,
|
||||
LexicalPencilFill,
|
||||
LexicalDraggableBlockMenu,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,224 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.49.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed issue where camera was not accessible for Moments on Android ([ffbc984](https://github.com/standardnotes/app/commit/ffbc984c5851f3cd286cd0a832111ba707bbd8f5))
|
||||
|
||||
## [3.49.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where add button would not be visible at times on iOS ([#2087](https://github.com/standardnotes/app/issues/2087)) ([c928f8a](https://github.com/standardnotes/app/commit/c928f8a281ad4e45c79c6c43f009175e5cbbed46))
|
||||
|
||||
## [3.49.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** PDF preview causing loop when biometrics are set to Immediate ([#2080](https://github.com/standardnotes/app/issues/2080)) ([0c23e4d](https://github.com/standardnotes/app/commit/0c23e4d36cb46cfe66d47bb32a753eacd37eba63))
|
||||
|
||||
# [3.49.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
## [3.48.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.48.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* display number of files for 'Files' view ([#2065](https://github.com/standardnotes/app/issues/2065)) ([767d354](https://github.com/standardnotes/app/commit/767d3547804bc98693dcc769baeed943c24426f3))
|
||||
|
||||
## [3.47.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Super editor popover menus on iOS ([#2061](https://github.com/standardnotes/app/issues/2061)) ([148aee4](https://github.com/standardnotes/app/commit/148aee441b816c58599d95db2e5b0f3f93414511))
|
||||
|
||||
## [3.47.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.47.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [3.46.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user