mirror of
https://github.com/standardnotes/app
synced 2026-09-13 18:46:08 -04:00
Compare commits
263
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef6e971592 | ||
|
|
1c48dd135c | ||
|
|
495786514e | ||
|
|
cfd6cf5932 | ||
|
|
8c5944f233 | ||
|
|
2b63484758 | ||
|
|
ccb15faff5 | ||
|
|
dc33aef660 | ||
|
|
771758fcd4 | ||
|
|
3fbe28e068 | ||
|
|
e67240821c | ||
|
|
d7c6ef6134 | ||
|
|
8311271a49 | ||
|
|
4e61d9b3e3 | ||
|
|
dd41082e71 | ||
|
|
adc6e9a353 | ||
|
|
bfd9f37331 | ||
|
|
044a4fc36b | ||
|
|
fe4841c631 | ||
|
|
14a98b5736 | ||
|
|
326e1a455b | ||
|
|
c3c8c2b9ce | ||
|
|
5e79cf90f4 | ||
|
|
e798b71b9f | ||
|
|
afd89c387f | ||
|
|
6bf6924a8b | ||
|
|
0f7b1539e0 | ||
|
|
e1c21d5fbd | ||
|
|
52d0fed437 | ||
|
|
caee8f1c6f | ||
|
|
40194d010f | ||
|
|
0373bf9a2d | ||
|
|
d7eb43401a | ||
|
|
a5a866a62a | ||
|
|
6090962b2c | ||
|
|
a9445dfcca | ||
|
|
b3fe769a60 | ||
|
|
fc6d28d166 | ||
|
|
03eac4353d | ||
|
|
58d84c47c1 | ||
|
|
577318e208 | ||
|
|
b8da877804 | ||
|
|
17f4d20fb5 | ||
|
|
4f07b915f0 | ||
|
|
f2f61b18e2 | ||
|
|
9fb506e532 | ||
|
|
3b64acc290 | ||
|
|
e6824a3414 | ||
|
|
08985bb5c8 | ||
|
|
77cb6e7891 | ||
|
|
7e7c5e14ab | ||
|
|
631be2e494 | ||
|
|
fe36009482 | ||
|
|
e77e852801 | ||
|
|
c59d824240 | ||
|
|
2f3bc9b85b | ||
|
|
1ec173dcd6 | ||
|
|
35b00737bf | ||
|
|
f3e147f4c9 | ||
|
|
63fdb328cb | ||
|
|
94c280256e | ||
|
|
cf0dc19f63 | ||
|
|
2696b07759 | ||
|
|
2cc5afb00a | ||
|
|
1828f80ba5 | ||
|
|
9c260c02f1 | ||
|
|
e4b3028d67 | ||
|
|
eceb46bed7 | ||
|
|
b9e0d0d154 | ||
|
|
a7add80633 | ||
|
|
0941183c8b | ||
|
|
c94035c1d6 | ||
|
|
343c39e873 | ||
|
|
889f245ab9 | ||
|
|
e8ada146e1 | ||
|
|
c9c5e915ce | ||
|
|
a1ec13d0b0 | ||
|
|
31bb03943d | ||
|
|
2b84c242f7 | ||
|
|
b8bf8ca112 | ||
|
|
c914b88e2d | ||
|
|
0169224bf2 | ||
|
|
4049b19170 | ||
|
|
df4148f824 | ||
|
|
91f3295cff | ||
|
|
c9078f374b | ||
|
|
bb4f1ff099 | ||
|
|
abdaec89b7 | ||
|
|
f559442a67 | ||
|
|
51aed0a518 | ||
|
|
35bb671e31 | ||
|
|
d76b190aba | ||
|
|
d6fd85e451 | ||
|
|
c90087462f | ||
|
|
58838a44cc | ||
|
|
0223800fdf | ||
|
|
318b1fdb0f | ||
|
|
fb517a91b0 | ||
|
|
c7b158eb64 | ||
|
|
d4b63e4ea6 | ||
|
|
cd8596b14e | ||
|
|
6c11766051 | ||
|
|
7f41564328 | ||
|
|
4e576d7526 | ||
|
|
317c6555d2 | ||
|
|
220f62665e | ||
|
|
1dc9086257 | ||
|
|
d149b500ee | ||
|
|
ad70bea368 | ||
|
|
3bcd969f83 | ||
|
|
d4af612fea | ||
|
|
b068b814f2 | ||
|
|
51de012236 | ||
|
|
2694c2f1a9 | ||
|
|
d3609853c6 | ||
|
|
a7ac715499 | ||
|
|
5ac93a6bd8 | ||
|
|
be4cc4e605 | ||
|
|
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 |
@@ -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
|
||||
|
||||
@@ -64,14 +64,14 @@ jobs:
|
||||
|
||||
- name: Run E2E test suite
|
||||
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
|
||||
uses: convictional/trigger-workflow-and-wait@v1.6.1
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: e2e
|
||||
repo: self-hosted
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-stable-server.yml
|
||||
workflow_file_name: testing-with-updating-client-and-server.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"image_tag": "${{ github.sha }}"}'
|
||||
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
trigger_workflow: true
|
||||
wait_workflow: true
|
||||
|
||||
@@ -40,14 +40,14 @@ jobs:
|
||||
docker tag standardnotes/snjs:${{ github.sha }} standardnotes/snjs:test
|
||||
docker push standardnotes/snjs:test
|
||||
- name: Run E2E test suite
|
||||
uses: convictional/trigger-workflow-and-wait@v1.6.1
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: e2e
|
||||
repo: self-hosted
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-stable-server.yml
|
||||
workflow_file_name: testing-with-updating-client-and-server.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"image_tag": "${{ github.sha }}"}'
|
||||
client_payload: '{"snjs_image_tag": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
trigger_workflow: true
|
||||
wait_workflow: true
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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,74 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** handling expired token responses ([#2113](https://github.com/standardnotes/app/issues/2113)) ([2696b07](https://github.com/standardnotes/app/commit/2696b07759104fb1d40781897f69e63133ad824a))
|
||||
|
||||
## [1.20.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** refreshing sessions ([#2106](https://github.com/standardnotes/app/issues/2106)) ([bb4f1ff](https://github.com/standardnotes/app/commit/bb4f1ff0990eb5a6f64e76a699fb95540cffdbb4))
|
||||
|
||||
## [1.20.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [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
|
||||
|
||||
@@ -6,12 +6,4 @@ module.exports = {
|
||||
transform: {
|
||||
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
|
||||
},
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 22,
|
||||
functions: 69,
|
||||
lines: 67,
|
||||
statements: 67
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.8",
|
||||
"version": "1.20.24",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint:fix": "eslint src --ext .ts --fix",
|
||||
"test": "jest spec --coverage"
|
||||
"test": "jest spec --coverage --passWithNoTests"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.3",
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.45.0",
|
||||
"@standardnotes/domain-core": "^1.11.0",
|
||||
"@standardnotes/encryption": "workspace:*",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
|
||||
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||
import { SubscriptionInviteResponse } from '../../Response/Subscription/SubscriptionInviteResponse'
|
||||
import { SubscriptionServerInterface } from '../../Server/Subscription/SubscriptionServerInterface'
|
||||
|
||||
import { SubscriptionApiOperations } from './SubscriptionApiOperations'
|
||||
import { SubscriptionApiService } from './SubscriptionApiService'
|
||||
|
||||
describe('SubscriptionApiService', () => {
|
||||
let subscriptionServer: SubscriptionServerInterface
|
||||
|
||||
const createService = () => new SubscriptionApiService(subscriptionServer)
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionServer = {} as jest.Mocked<SubscriptionServerInterface>
|
||||
subscriptionServer.invite = jest.fn().mockReturnValue({
|
||||
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
|
||||
} as jest.Mocked<SubscriptionInviteResponse>)
|
||||
subscriptionServer.cancelInvite = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<SubscriptionInviteCancelResponse>)
|
||||
subscriptionServer.listInvites = jest.fn().mockReturnValue({
|
||||
data: { invitations: [{} as jest.Mocked<Invitation>] },
|
||||
} as jest.Mocked<SubscriptionInviteListResponse>)
|
||||
subscriptionServer.acceptInvite = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
|
||||
})
|
||||
|
||||
it('should invite a user', async () => {
|
||||
const response = await createService().invite('[email protected]')
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
expect(subscriptionServer.invite).toHaveBeenCalledWith({
|
||||
api: '20200115',
|
||||
identifier: '[email protected]',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not invite a user if it is already inviting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[SubscriptionApiOperations.Inviting, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.invite('[email protected]')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not invite a user if the server fails', async () => {
|
||||
subscriptionServer.invite = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().invite('[email protected]')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should cancel an invite', async () => {
|
||||
const response = await createService().cancelInvite('1-2-3')
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(subscriptionServer.cancelInvite).toHaveBeenCalledWith({
|
||||
api: '20200115',
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not cancel an invite if it is already canceling', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[SubscriptionApiOperations.CancelingInvite, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.cancelInvite('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not cancel an invite if the server fails', async () => {
|
||||
subscriptionServer.cancelInvite = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().cancelInvite('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should list invites', async () => {
|
||||
const response = await createService().listInvites()
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
invitations: [{} as jest.Mocked<Invitation>],
|
||||
},
|
||||
})
|
||||
expect(subscriptionServer.listInvites).toHaveBeenCalledWith({
|
||||
api: '20200115',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not list invitations if it is already listing', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[SubscriptionApiOperations.ListingInvites, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.listInvites()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not list invites if the server fails', async () => {
|
||||
subscriptionServer.listInvites = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().listInvites()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should accept an invite', async () => {
|
||||
const response = await createService().acceptInvite('1-2-3')
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(subscriptionServer.acceptInvite).toHaveBeenCalledWith({
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not accept an invite if it is already accepting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[SubscriptionApiOperations.AcceptingInvite, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.acceptInvite('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not accept an invite if the server fails', async () => {
|
||||
subscriptionServer.acceptInvite = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().acceptInvite('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,197 +0,0 @@
|
||||
import { ProtocolVersion, UserRequestType } from '@standardnotes/common'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
import { UserServerInterface } from '../../Server'
|
||||
import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface'
|
||||
|
||||
import { UserApiOperations } from './UserApiOperations'
|
||||
import { UserApiService } from './UserApiService'
|
||||
|
||||
describe('UserApiService', () => {
|
||||
let userServer: UserServerInterface
|
||||
let userRequestServer: UserRequestServerInterface
|
||||
let keyParams: RootKeyParamsInterface
|
||||
|
||||
const createService = () => new UserApiService(userServer, userRequestServer)
|
||||
|
||||
beforeEach(() => {
|
||||
userServer = {} as jest.Mocked<UserServerInterface>
|
||||
userServer.register = jest.fn().mockReturnValue({
|
||||
data: { user: { email: '[email protected]', uuid: '1-2-3' } },
|
||||
} as jest.Mocked<UserRegistrationResponse>)
|
||||
userServer.deleteAccount = jest.fn().mockReturnValue({
|
||||
data: { message: 'Success' },
|
||||
} as jest.Mocked<UserDeletionResponse>)
|
||||
|
||||
userRequestServer = {} as jest.Mocked<UserRequestServerInterface>
|
||||
userRequestServer.submitUserRequest = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<UserRequestResponse>)
|
||||
|
||||
keyParams = {} as jest.Mocked<RootKeyParamsInterface>
|
||||
keyParams.getPortableValue = jest.fn().mockReturnValue({
|
||||
identifier: '[email protected]',
|
||||
version: ProtocolVersion.V004,
|
||||
})
|
||||
})
|
||||
|
||||
it('should register a user', async () => {
|
||||
const response = await createService().register({
|
||||
email: '[email protected]',
|
||||
serverPassword: 'testpasswd',
|
||||
keyParams,
|
||||
ephemeral: false,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
user: {
|
||||
email: '[email protected]',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(userServer.register).toHaveBeenCalledWith({
|
||||
api: '20200115',
|
||||
email: '[email protected]',
|
||||
ephemeral: false,
|
||||
identifier: '[email protected]',
|
||||
password: 'testpasswd',
|
||||
version: '004',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not register a user if it is already registering', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.Registering, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.register({ email: '[email protected]', serverPassword: 'testpasswd', keyParams, ephemeral: false })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not register a user if the server fails', async () => {
|
||||
userServer.register = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().register({
|
||||
email: '[email protected]',
|
||||
serverPassword: 'testpasswd',
|
||||
keyParams,
|
||||
ephemeral: false,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should submit a user request', async () => {
|
||||
const response = await createService().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(userRequestServer.submitUserRequest).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not submit a user request if it is already submitting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.SubmittingRequest, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.submitUserRequest({ userUuid: '1-2-3', requestType: UserRequestType.ExitDiscount })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not submit a user request if the server fails', async () => {
|
||||
userRequestServer.submitUserRequest = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should delete a user', async () => {
|
||||
const response = await createService().deleteAccount('1-2-3')
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
message: 'Success',
|
||||
},
|
||||
})
|
||||
expect(userServer.deleteAccount).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not delete a user if it is already deleting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[UserApiOperations.DeletingAccount, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.deleteAccount('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not delete a user if the server fails', async () => {
|
||||
userServer.deleteAccount = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().deleteAccount('1-2-3')
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
import { WebSocketConnectionTokenResponse } from '../../Response'
|
||||
|
||||
import { WebSocketServerInterface } from '../../Server/WebSocket/WebSocketServerInterface'
|
||||
import { WebSocketApiOperations } from './WebSocketApiOperations'
|
||||
|
||||
import { WebSocketApiService } from './WebSocketApiService'
|
||||
|
||||
describe('WebSocketApiService', () => {
|
||||
let webSocketServer: WebSocketServerInterface
|
||||
|
||||
const createService = () => new WebSocketApiService(webSocketServer)
|
||||
|
||||
beforeEach(() => {
|
||||
webSocketServer = {} as jest.Mocked<WebSocketServerInterface>
|
||||
webSocketServer.createConnectionToken = jest.fn().mockReturnValue({
|
||||
data: { token: 'foobar' },
|
||||
} as jest.Mocked<WebSocketConnectionTokenResponse>)
|
||||
})
|
||||
|
||||
it('should create a websocket connection token', async () => {
|
||||
const response = await createService().createConnectionToken()
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
token: 'foobar',
|
||||
},
|
||||
})
|
||||
expect(webSocketServer.createConnectionToken).toHaveBeenCalledWith({})
|
||||
})
|
||||
|
||||
it('should not create a token if it is already creating', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WebSocketApiOperations.CreatingConnectionToken, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.createConnectionToken()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not create a token if the server fails', async () => {
|
||||
webSocketServer.createConnectionToken = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().createConnectionToken()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,368 +0,0 @@
|
||||
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { HttpStatusCode } from '../../Http'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
|
||||
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
|
||||
import { WorkspaceApiService } from './WorkspaceApiService'
|
||||
|
||||
describe('WorkspaceApiService', () => {
|
||||
let workspaceServer: WorkspaceServerInterface
|
||||
|
||||
const createService = () => new WorkspaceApiService(workspaceServer)
|
||||
|
||||
beforeEach(() => {
|
||||
workspaceServer = {} as jest.Mocked<WorkspaceServerInterface>
|
||||
workspaceServer.createWorkspace = jest.fn().mockReturnValue({
|
||||
data: { uuid: '1-2-3' },
|
||||
} as jest.Mocked<WorkspaceCreationResponse>)
|
||||
workspaceServer.inviteToWorkspace = jest.fn().mockReturnValue({
|
||||
data: { uuid: 'i-1-2-3' },
|
||||
} as jest.Mocked<WorkspaceInvitationResponse>)
|
||||
workspaceServer.acceptInvite = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
|
||||
workspaceServer.listWorkspaces = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
} as jest.Mocked<WorkspaceListResponse>)
|
||||
workspaceServer.listWorkspaceUsers = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { users: [] },
|
||||
} as jest.Mocked<WorkspaceUserListResponse>)
|
||||
workspaceServer.initiateKeyshare = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
|
||||
})
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createService().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.createWorkspace).toHaveBeenCalledWith({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
workspaceType: 'private',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not create a workspace if it is already creating', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Creating, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not create a workspace if the server fails', async () => {
|
||||
workspaceServer.createWorkspace = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should invite to a workspace', async () => {
|
||||
const response = await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: 'i-1-2-3',
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.inviteToWorkspace).toHaveBeenCalledWith({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: 'write-and-read',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not invite to a workspace if it is already inviting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Inviting, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not invite to a workspace if the server fails', async () => {
|
||||
workspaceServer.inviteToWorkspace = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().inviteToWorkspace({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
inviteeEmail: '[email protected]',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should accept invite to a workspace', async () => {
|
||||
const response = await createService().acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.acceptInvite).toHaveBeenCalledWith({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not accept invite to a workspace if it is already accepting', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Accepting, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not accept invite to a workspace if the server fails', async () => {
|
||||
workspaceServer.acceptInvite = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().acceptInvite({
|
||||
userUuid: 'u-1-2-3',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'foo',
|
||||
encryptedPrivateKey: 'bar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should list workspaces', async () => {
|
||||
const response = await createService().listWorkspaces()
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
ownedWorkspaces: [],
|
||||
joinedWorkspaces: [],
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.listWorkspaces).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not list workspaces if it is already listing them', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaces, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.listWorkspaces()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not list workspaces if the server fails', async () => {
|
||||
workspaceServer.listWorkspaces = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().listWorkspaces()
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should list workspace users', async () => {
|
||||
const response = await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
users: [],
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.listWorkspaceUsers).toHaveBeenCalledWith({ workspaceUuid: 'w-1-2-3' })
|
||||
})
|
||||
|
||||
it('should not list workspace users if it is already listing them', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.ListingWorkspaceUsers, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not list workspace users if the server fails', async () => {
|
||||
workspaceServer.listWorkspaceUsers = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should initiate keyshare in workspace for user', async () => {
|
||||
const response = await createService().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.initiateKeyshare).toHaveBeenCalledWith({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not initiate keyshare in workspace if it is already initiating', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.InitiatingKeyshare, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.initiateKeyshare({ workspaceUuid: 'w-1-2-3', userUuid: 'u-1-2-3', encryptedWorkspaceKey: 'foobar' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not initiate keyshare in workspace if the server fails', async () => {
|
||||
workspaceServer.initiateKeyshare = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Environment } from '@standardnotes/models'
|
||||
|
||||
import { HttpResponseMeta } from './HttpResponseMeta'
|
||||
import { HttpService } from './HttpService'
|
||||
|
||||
describe('HttpService', () => {
|
||||
const environment = Environment.Web
|
||||
const appVersion = '1.2.3'
|
||||
const snjsVersion = '2.3.4'
|
||||
const host = 'http://bar'
|
||||
let updateMetaCallback: (meta: HttpResponseMeta) => void
|
||||
|
||||
const createService = () => {
|
||||
const service = new HttpService(environment, appVersion, snjsVersion, updateMetaCallback)
|
||||
service.setHost(host)
|
||||
return service
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
updateMetaCallback = jest.fn()
|
||||
})
|
||||
|
||||
it('should set host', () => {
|
||||
const service = createService()
|
||||
|
||||
expect(service['host']).toEqual('http://bar')
|
||||
|
||||
service.setHost('http://foo')
|
||||
|
||||
expect(service['host']).toEqual('http://foo')
|
||||
})
|
||||
|
||||
it('should set and use the authorization token', () => {
|
||||
const service = createService()
|
||||
|
||||
expect(service['authorizationToken']).toBeUndefined()
|
||||
|
||||
service.setAuthorizationToken('foo-bar')
|
||||
|
||||
expect(service['authorizationToken']).toEqual('foo-bar')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { isString, joinPaths, sleep } from '@standardnotes/utils'
|
||||
import { Environment } from '@standardnotes/models'
|
||||
import { Session, SessionToken } from '@standardnotes/domain-core'
|
||||
|
||||
import { HttpRequestParams } from './HttpRequestParams'
|
||||
import { HttpVerb } from './HttpVerb'
|
||||
import { HttpRequest } from './HttpRequest'
|
||||
@@ -10,21 +12,26 @@ import { XMLHttpRequestState } from './XMLHttpRequestState'
|
||||
import { ErrorMessage } from '../Error/ErrorMessage'
|
||||
import { HttpResponseMeta } from './HttpResponseMeta'
|
||||
import { HttpErrorResponseBody } from './HttpErrorResponseBody'
|
||||
import { Paths } from '../Server/Auth/Paths'
|
||||
import { SessionRefreshResponse } from '../Response/Auth/SessionRefreshResponse'
|
||||
|
||||
export class HttpService implements HttpServiceInterface {
|
||||
private authorizationToken?: string
|
||||
private session: Session | null
|
||||
private __latencySimulatorMs?: number
|
||||
private host!: string
|
||||
private declare host: string
|
||||
|
||||
constructor(
|
||||
private environment: Environment,
|
||||
private appVersion: string,
|
||||
private snjsVersion: string,
|
||||
private updateMetaCallback: (meta: HttpResponseMeta) => void,
|
||||
) {}
|
||||
private refreshSessionCallback: (session: Session) => void,
|
||||
) {
|
||||
this.session = null
|
||||
}
|
||||
|
||||
setAuthorizationToken(authorizationToken: string): void {
|
||||
this.authorizationToken = authorizationToken
|
||||
setSession(session: Session): void {
|
||||
this.session = session
|
||||
}
|
||||
|
||||
setHost(host: string): void {
|
||||
@@ -36,7 +43,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Get,
|
||||
authentication: authentication ?? this.authorizationToken,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,7 +52,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Post,
|
||||
authentication: authentication ?? this.authorizationToken,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +61,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Put,
|
||||
authentication: authentication ?? this.authorizationToken,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,7 +70,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Patch,
|
||||
authentication: authentication ?? this.authorizationToken,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +79,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Delete,
|
||||
authentication: authentication ?? this.authorizationToken,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,9 +96,68 @@ export class HttpService implements HttpServiceInterface {
|
||||
this.updateMetaCallback(response.meta)
|
||||
}
|
||||
|
||||
if (response.status === HttpStatusCode.ExpiredAccessToken) {
|
||||
const isSessionRefreshed = await this.refreshSession()
|
||||
if (!isSessionRefreshed) {
|
||||
return response
|
||||
}
|
||||
|
||||
httpRequest.authentication = this.session?.accessToken.value
|
||||
|
||||
return this.runHttp(httpRequest)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private async refreshSession(): Promise<boolean> {
|
||||
if (this.session === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const response = (await this.post(Paths.v1.refreshSession, {
|
||||
access_token: this.session.accessToken.value,
|
||||
refresh_token: this.session.refreshToken.value,
|
||||
})) as SessionRefreshResponse
|
||||
|
||||
if (response.data.error) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.meta) {
|
||||
this.updateMetaCallback(response.meta)
|
||||
}
|
||||
|
||||
const accessTokenOrError = SessionToken.create(
|
||||
response.data.session.access_token,
|
||||
response.data.session.access_expiration,
|
||||
)
|
||||
if (accessTokenOrError.isFailed()) {
|
||||
return false
|
||||
}
|
||||
const accessToken = accessTokenOrError.getValue()
|
||||
|
||||
const refreshTokenOrError = SessionToken.create(
|
||||
response.data.session.refresh_token,
|
||||
response.data.session.refresh_expiration,
|
||||
)
|
||||
if (refreshTokenOrError.isFailed()) {
|
||||
return false
|
||||
}
|
||||
const refreshToken = refreshTokenOrError.getValue()
|
||||
|
||||
const sessionOrError = Session.create(accessToken, refreshToken, response.data.session.readonly_access)
|
||||
if (sessionOrError.isFailed()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.setSession(sessionOrError.getValue())
|
||||
|
||||
this.refreshSessionCallback(this.session)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
|
||||
if (
|
||||
httpRequest.params !== undefined &&
|
||||
@@ -199,13 +265,12 @@ export class HttpService implements HttpServiceInterface {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.MultipleChoices) {
|
||||
resolve(response)
|
||||
} else {
|
||||
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
|
||||
if (httpStatus === HttpStatusCode.Forbidden && response.data && response.data.error !== undefined) {
|
||||
;(response.data as HttpErrorResponseBody).error.message = ErrorMessage.RateLimited
|
||||
}
|
||||
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Session } from '@standardnotes/domain-core'
|
||||
|
||||
import { HttpRequestParams } from './HttpRequestParams'
|
||||
import { HttpResponse } from './HttpResponse'
|
||||
|
||||
export interface HttpServiceInterface {
|
||||
setHost(host: string): void
|
||||
setAuthorizationToken(authorizationToken: string): void
|
||||
setSession(session: Session): void
|
||||
get(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
|
||||
post(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
|
||||
put(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse>
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum HttpStatusCode {
|
||||
Forbidden = 403,
|
||||
Gone = 410,
|
||||
ExpiredAccessToken = 498,
|
||||
InternalServerError = 500,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { SessionRefreshResponseBody } from './SessionRefreshResponseBody'
|
||||
|
||||
export interface SessionRefreshResponse extends HttpResponse {
|
||||
data: Either<SessionRefreshResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export type SessionRefreshResponseBody = {
|
||||
session: {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
access_expiration: number
|
||||
refresh_expiration: number
|
||||
readonly_access: boolean
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const SessionPaths = {
|
||||
refreshSession: '/v1/sessions/refresh',
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
v1: {
|
||||
...SessionPaths,
|
||||
},
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { SubscriptionInviteResponse } from '../../Response'
|
||||
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
|
||||
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||
import { SubscriptionInviteDeclineResponse } from '../../Response/Subscription/SubscriptionInviteDeclineResponse'
|
||||
import { SubscriptionInviteListResponse } from '../../Response/Subscription/SubscriptionInviteListResponse'
|
||||
|
||||
import { SubscriptionServer } from './SubscriptionServer'
|
||||
|
||||
describe('SubscriptionServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new SubscriptionServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
})
|
||||
|
||||
it('should invite a user to a shared subscription', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true, sharedSubscriptionInvitationUuid: '1-2-3' },
|
||||
} as jest.Mocked<SubscriptionInviteResponse>)
|
||||
|
||||
const response = await createServer().invite({
|
||||
api: ApiVersion.v0,
|
||||
identifier: '[email protected]',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should accept an invite to a shared subscription', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<SubscriptionInviteAcceptResponse>)
|
||||
|
||||
const response = await createServer().acceptInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should decline an invite to a shared subscription', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<SubscriptionInviteDeclineResponse>)
|
||||
|
||||
const response = await createServer().declineInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should cancel an invite to a shared subscription', async () => {
|
||||
httpService.delete = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<SubscriptionInviteCancelResponse>)
|
||||
|
||||
const response = await createServer().cancelInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should list invitations to a shared subscription', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
data: { invitations: [{} as jest.Mocked<Invitation>] },
|
||||
} as jest.Mocked<SubscriptionInviteListResponse>)
|
||||
|
||||
const response = await createServer().listInvites({
|
||||
api: ApiVersion.v0,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
invitations: [{} as jest.Mocked<Invitation>],
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,54 +0,0 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { UserDeletionResponse, UserRegistrationResponse } from '../../Response'
|
||||
import { UserServer } from './UserServer'
|
||||
|
||||
describe('UserServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new UserServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { user: { email: '[email protected]', uuid: '1-2-3' } },
|
||||
} as jest.Mocked<UserRegistrationResponse>)
|
||||
httpService.delete = jest.fn().mockReturnValue({
|
||||
data: { message: 'Success' },
|
||||
} as jest.Mocked<UserDeletionResponse>)
|
||||
})
|
||||
|
||||
it('should register a user', async () => {
|
||||
const response = await createServer().register({
|
||||
password: 'test',
|
||||
api: ApiVersion.v0,
|
||||
email: '[email protected]',
|
||||
ephemeral: false,
|
||||
version: ProtocolVersion.V004,
|
||||
pw_nonce: 'test',
|
||||
identifier: '[email protected]',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
user: {
|
||||
email: '[email protected]',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should delete a user', async () => {
|
||||
const response = await createServer().deleteAccount({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
message: 'Success',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,32 +0,0 @@
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse'
|
||||
|
||||
import { UserRequestServer } from './UserRequestServer'
|
||||
|
||||
describe('UserRequestServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new UserRequestServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<UserRequestResponse>)
|
||||
})
|
||||
|
||||
it('should submit a user request', async () => {
|
||||
const response = await createServer().submitUserRequest({
|
||||
userUuid: '1-2-3',
|
||||
requestType: UserRequestType.ExitDiscount,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,27 +0,0 @@
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { WebSocketConnectionTokenResponse } from '../../Response'
|
||||
|
||||
import { WebSocketServer } from './WebSocketServer'
|
||||
|
||||
describe('WebSocketServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new WebSocketServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { token: 'foobar' },
|
||||
} as jest.Mocked<WebSocketConnectionTokenResponse>)
|
||||
})
|
||||
|
||||
it('should create a websocket connection token', async () => {
|
||||
const response = await createServer().createConnectionToken({})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
token: 'foobar',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,124 +0,0 @@
|
||||
import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
|
||||
|
||||
import { HttpServiceInterface, HttpStatusCode } from '../../Http'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceInvitationAcceptingResponse } from '../../Response/Workspace/WorkspaceInvitationAcceptingResponse'
|
||||
import { WorkspaceInvitationResponse } from '../../Response/Workspace/WorkspaceInvitationResponse'
|
||||
import { WorkspaceKeyshareInitiatingResponse } from '../../Response/Workspace/WorkspaceKeyshareInitiatingResponse'
|
||||
import { WorkspaceListResponse } from '../../Response/Workspace/WorkspaceListResponse'
|
||||
import { WorkspaceUserListResponse } from '../../Response/Workspace/WorkspaceUserListResponse'
|
||||
|
||||
import { WorkspaceServer } from './WorkspaceServer'
|
||||
|
||||
describe('WorkspaceServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new WorkspaceServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { uuid: '1-2-3' },
|
||||
} as jest.Mocked<WorkspaceCreationResponse>)
|
||||
})
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createServer().createWorkspace({
|
||||
workspaceType: WorkspaceType.Private,
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should inivte to a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { uuid: 'i-1-2-3' },
|
||||
} as jest.Mocked<WorkspaceInvitationResponse>)
|
||||
|
||||
const response = await createServer().inviteToWorkspace({
|
||||
inviteeEmail: '[email protected]',
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
accessLevel: WorkspaceAccessLevel.WriteAndRead,
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: 'i-1-2-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should accept invitation to a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceInvitationAcceptingResponse>)
|
||||
|
||||
const response = await createServer().acceptInvite({
|
||||
encryptedPrivateKey: 'foo',
|
||||
inviteUuid: 'i-1-2-3',
|
||||
publicKey: 'bar',
|
||||
userUuid: 'u-1-2-3',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should list workspaces', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
} as jest.Mocked<WorkspaceListResponse>)
|
||||
|
||||
const response = await createServer().listWorkspaces({})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { ownedWorkspaces: [], joinedWorkspaces: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('should list workspace users', async () => {
|
||||
httpService.get = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { users: [] },
|
||||
} as jest.Mocked<WorkspaceUserListResponse>)
|
||||
|
||||
const response = await createServer().listWorkspaceUsers({ workspaceUuid: 'w-1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { users: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('should initiate keyshare for user in a workspace', async () => {
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: true },
|
||||
} as jest.Mocked<WorkspaceKeyshareInitiatingResponse>)
|
||||
|
||||
const response = await createServer().initiateKeyshare({
|
||||
workspaceUuid: 'w-1-2-3',
|
||||
userUuid: 'u-1-2-3',
|
||||
encryptedWorkspaceKey: 'foobar',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: {
|
||||
success: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,104 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
# [1.12.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-26)
|
||||
|
||||
### Features
|
||||
|
||||
* Clicking the link option in Super notes will automatically add the selected text as the link ([326e1a4](https://github.com/standardnotes/app/commit/326e1a455bb510d795f357123f617618267848cc))
|
||||
|
||||
## [1.11.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.11.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.11.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-24)
|
||||
|
||||
### Features
|
||||
|
||||
* Persist collapsible block state in Super notes ([#2119](https://github.com/standardnotes/app/issues/2119)) ([577318e](https://github.com/standardnotes/app/commit/577318e208367dc52caf2d76fd87419eaead7ed4))
|
||||
|
||||
## [1.10.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.10.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
# [1.10.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Features
|
||||
|
||||
* add ability to convert selection in Super to bulleted or numbered list ([be4cc4e](https://github.com/standardnotes/app/commit/be4cc4e605100e0a86de214e548e9ed10e2568aa))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@standardnotes/blocks-editor",
|
||||
"version": "1.6.2",
|
||||
"version": "1.12.1",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"tsc": "tsc -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lexical/react": "^0.6.0",
|
||||
"@lexical/react": "0.7.5",
|
||||
"@standardnotes/icons": "workspace:*",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"lexical": "^0.6.0",
|
||||
"lexical": "0.7.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
@@ -28,16 +21,20 @@ import DraggableBlockPlugin from '../Lexical/Plugins/DraggableBlockPlugin';
|
||||
import CodeHighlightPlugin from '../Lexical/Plugins/CodeHighlightPlugin';
|
||||
import FloatingTextFormatToolbarPlugin from '../Lexical/Plugins/FloatingTextFormatToolbarPlugin';
|
||||
import FloatingLinkEditorPlugin from '../Lexical/Plugins/FloatingLinkEditorPlugin';
|
||||
import {TabIndentationPlugin} from '../Lexical/Plugins/TabIndentationPlugin';
|
||||
import {truncateString} from './Utils';
|
||||
import {SuperEditorContentId} from './Constants';
|
||||
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> = ({
|
||||
@@ -47,6 +44,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewLength,
|
||||
spellcheck,
|
||||
ignoreFirstChange = false,
|
||||
readonly,
|
||||
}) => {
|
||||
const [didIgnoreFirstChange, setDidIgnoreFirstChange] = useState(false);
|
||||
const handleChange = useCallback(
|
||||
@@ -65,11 +63,14 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewText += '\n';
|
||||
}
|
||||
});
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
|
||||
if (previewLength) {
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
}
|
||||
|
||||
try {
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange(stringifiedEditorState, previewText);
|
||||
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}`,
|
||||
@@ -98,29 +99,24 @@ 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>
|
||||
</div>
|
||||
}
|
||||
placeholder=""
|
||||
placeholder={null}
|
||||
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 />
|
||||
@@ -130,7 +126,8 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<TwitterPlugin />
|
||||
<YouTubePlugin />
|
||||
<CollapsiblePlugin />
|
||||
{floatingAnchorElem && (
|
||||
<TabIndentationPlugin />
|
||||
{!readonly && floatingAnchorElem && (
|
||||
<>
|
||||
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
|
||||
<FloatingLinkEditorPlugin />
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user