mirror of
https://github.com/standardnotes/app
synced 2026-09-18 06:45:52 -04:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36288ad36a | ||
|
|
2573407851 | ||
|
|
baf7fb0019 | ||
|
|
9ea1b35e0f | ||
|
|
98fc870d76 | ||
|
|
4d30331861 | ||
|
|
12e2b83344 | ||
|
|
9e835c3b07 | ||
|
|
12168945f1 | ||
|
|
a827274ac6 | ||
|
|
65c377ba62 | ||
|
|
c02ec9a5f9 | ||
|
|
40b344fd00 | ||
|
|
776fa579c4 | ||
|
|
b174493b4e | ||
|
|
a7a81b15a9 | ||
|
|
135e5924bf | ||
|
|
aaf68a8f73 | ||
|
|
9a297fe8cf | ||
|
|
f06167abc2 | ||
|
|
f125ad3cf1 | ||
|
|
26949749fc |
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Sync Repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
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
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
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
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
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
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
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
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -52,17 +52,20 @@ jobs:
|
||||
run: yarn test
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Publish SNJS Docker image for E2E testing
|
||||
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
|
||||
run: |
|
||||
yarn docker build @standardnotes/snjs -t standardnotes/snjs:${{ github.sha }}
|
||||
docker push standardnotes/snjs:${{ github.sha }}
|
||||
|
||||
- name: Run E2E test suite
|
||||
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
|
||||
uses: convictional/[email protected]
|
||||
with:
|
||||
owner: standardnotes
|
||||
@@ -84,6 +87,7 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.CI_NPM_TOKEN }}
|
||||
|
||||
- name: Publish SNJS Docker image as stable
|
||||
if: "${{ contains(github.event.head_commit.message, 'skip e2e') == false }}"
|
||||
run: |
|
||||
docker tag standardnotes/snjs:${{ github.sha }} standardnotes/snjs:latest
|
||||
docker push standardnotes/snjs:latest
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- name: Login to Docker Hub
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Copy robots.txt
|
||||
run: cp packages/web-server/public/robots.txt.production packages/web-server/public/robots.txt
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Copy robots.txt
|
||||
run: cp packages/web-server/public/robots.txt.development packages/web-server/public/robots.txt
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
TestDockerBuild:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Copy default configuration
|
||||
|
||||
Binary file not shown.
@@ -71,18 +71,3 @@ You can configure the `DEFAULT_SYNC_SERVER` environment variable to set the defa
|
||||
```
|
||||
DEFAULT_SYNC_SERVER=https://sync.myserver
|
||||
```
|
||||
|
||||
## Running E2E tests
|
||||
|
||||
In order to run a stable server environment for E2E tests that will is up to date with what we have on production please checkout locally the [e2e repository](https://github.com/standardnotes/e2e).
|
||||
|
||||
To start the server type in the e2e repository:
|
||||
```
|
||||
yarn install --immutable
|
||||
yarn test:stable-server
|
||||
```
|
||||
|
||||
Then once the whole server infra is up and ready for your tests you can run the e2e suite in the local browser by typing (make sure you have built all the packages beforehand):
|
||||
```
|
||||
yarn start:server:e2e
|
||||
```
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.7.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ste:** revert border radius change ([b174493](https://github.com/standardnotes/app/commit/b174493b4ea3ca164cf5754ab791022b17b612ac))
|
||||
|
||||
## [2.7.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/components-meta
|
||||
|
||||
## [2.7.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+21
-21
@@ -35,19 +35,19 @@
|
||||
"binary": "8353de29ec88472a87fd24349844569e9e468893de40c1fafbb0a237fd7410dd"
|
||||
},
|
||||
"org.standardnotes.code-editor": {
|
||||
"version": "1.3.17",
|
||||
"base64": "2f061eeee557be892a1e6ea536eb715ba13344f1e0726112fefa83d300d1da96",
|
||||
"binary": "47f8d1e1b5aeaf078f8eb3d00ba38adc8e2e3a60b37c9091a2bbd5cdb082d931"
|
||||
"version": "1.4.0",
|
||||
"base64": "927b9165925ef7b1012d110f39238862d0dac5fd25b9468e81989e19c1daebf3",
|
||||
"binary": "dcc2fb1f923ea891e3d7856ff72c70277747ef2051794a0835cc79f9638b6d42"
|
||||
},
|
||||
"org.standardnotes.bold-editor": {
|
||||
"version": "1.3.10",
|
||||
"base64": "0015f9bde6e156cadf20b3ab80c864c6d73fc1de279b863963c24cc4d003c93c",
|
||||
"binary": "deaece4cd37801f796eca2bf37fcccabfc6ddc43581630aa9ed04b9b0f7a7762"
|
||||
"version": "1.4.0",
|
||||
"base64": "ad8e1dc97d49744be986608b1dd9e1205f15a4d1d1613e4a4f4da511944f1938",
|
||||
"binary": "f251b88605da4dc760213c0c7ed90bfccaa380854697bf94cca8b5764c9d976a"
|
||||
},
|
||||
"org.standardnotes.plus-editor": {
|
||||
"version": "1.7.0",
|
||||
"base64": "a63f79a3b6398ab13f8a4e106bbfc06312d31336967bbc5d2aa16c0c94ee08bb",
|
||||
"binary": "fd258ce43577024cbe297904963b1beabe239d053b7666b7e469cf1aaf0abd8d"
|
||||
"version": "1.7.1",
|
||||
"base64": "15e839a3ef30bc666ef9c7f3c682345e426be94a3ee8483146abf029db2336f2",
|
||||
"binary": "08995a1a0518920a81364e4064b7de6d859f2f13dd9b5f6beb518594b6e35be5"
|
||||
},
|
||||
"org.standardnotes.simple-markdown-editor": {
|
||||
"version": "1.4.7",
|
||||
@@ -55,9 +55,9 @@
|
||||
"binary": "1bd2863239e5ce41c56e3ccebe2cbd29c259abbe7124aed656e92d17b5428226"
|
||||
},
|
||||
"org.standardnotes.advanced-markdown-editor": {
|
||||
"version": "1.6.1",
|
||||
"base64": "51a2dc528ac2a6b1b853fa63e4b2c6082a651c7b7ff63baf47befaae5f4ec8f7",
|
||||
"binary": "b6a6a99420bc449dc2798ee292573f6f923b3e03cea022e8c7a5204f061eea58"
|
||||
"version": "1.6.2",
|
||||
"base64": "72f168a6236a7be819a1e945dd1b0fa069082d7c3b1c70724390489934184c26",
|
||||
"binary": "0a5e62fba43ac02c475fa4a9b3dc9c4cbb555fc70ca3fe6a3f29ee3c75954d66"
|
||||
},
|
||||
"org.standardnotes.minimal-markdown-editor": {
|
||||
"version": "1.3.13",
|
||||
@@ -75,19 +75,19 @@
|
||||
"binary": "c4479f8768f5f6cda41850212a8e03927990adfa33ebd36e29ee6de2335e8e46"
|
||||
},
|
||||
"org.standardnotes.simple-task-editor": {
|
||||
"version": "1.4.1",
|
||||
"base64": "9035b03e068138876802bcd28b1099b6ef22fac5ed51e0a9ec0152327a7f8a16",
|
||||
"binary": "e4549b5b3219de5a5c0799d9b33c815a5bb7d8d974ba45e476dbbe376bfa5937"
|
||||
"version": "1.4.3",
|
||||
"base64": "5e8f767ac00d5a5af9b3650e56230a2446db4656515fb88ee5293da989b0f172",
|
||||
"binary": "7c6f499b8107f80ec5fd84e591c2c33291d1fc6db85f098df4f8308a0cda901d"
|
||||
},
|
||||
"org.standardnotes.token-vault": {
|
||||
"version": "2.2.2",
|
||||
"base64": "cecf46bf3c959a180a96df4ea93ea30e2b7f8c88d67eef4a2f42eae58ede362b",
|
||||
"binary": "db4117a732db0f1d5bccb568088b8ae82d68af1430ba8d22810bedc8ddd63f6d"
|
||||
"version": "2.2.3",
|
||||
"base64": "bf5c3a2aae3f119043ee744ab5535de4705eeed67d5ecf9e1d7b95817f92bc19",
|
||||
"binary": "8ef8a1eaf19d22b0ac279ad0c4d27b29c057b0f9b0d5c5f4cd6183eb506feaa3"
|
||||
},
|
||||
"org.standardnotes.standard-sheets": {
|
||||
"version": "1.5.0",
|
||||
"base64": "5b91ed423b5c9526f3a6ccb4798feb0935095cb81aa90ae3a5964f0284b7b4c0",
|
||||
"binary": "12b19b16adf7dfb6626be2deaa8fe47a242b6fb15c841d6b12a1427a1a2fbd5e"
|
||||
"version": "1.5.1",
|
||||
"base64": "0ae54799c28a336447a63ddabfe43d0c6e18f8c2cd29cfdb1c0174ab96fde248",
|
||||
"binary": "54a01414a21e46c67fa59c69189a3d34a6548de7539699f2cf0248f9c5059d37"
|
||||
},
|
||||
"org.standardnotes.advanced-checklist": {
|
||||
"version": "0.2.3",
|
||||
|
||||
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.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/components-meta",
|
||||
"version": "2.7.5",
|
||||
"version": "2.7.7",
|
||||
"private": true,
|
||||
"author": "Standard Notes.",
|
||||
"main": "dist",
|
||||
|
||||
@@ -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.4.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Features
|
||||
|
||||
* deprecated editors ([#1166](https://github.com/standardnotes/app/issues/1166)) ([60ca415](https://github.com/standardnotes/app/commit/60ca4150446f9a14bb6a31416686c6d07a7d0cd9))
|
||||
|
||||
## [1.3.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/bold-editor
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/bold-editor",
|
||||
"private": true,
|
||||
"version": "1.3.10",
|
||||
"version": "1.4.0",
|
||||
"main": "dist/dist.js",
|
||||
"scripts": {
|
||||
"skip:lint": "eslint app/ --ext .js",
|
||||
@@ -39,7 +39,7 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"webpack-merge-and-include-globally": "^2.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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.
|
||||
|
||||
## [1.6.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/markdown-hybrid
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/markdown-hybrid
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/markdown-hybrid",
|
||||
"private": true,
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"description": "A Standard Notes derived editor that offers full support for Markdown editing.",
|
||||
"main": "dist/dist.js",
|
||||
"author": "Standard Notes <[email protected]>",
|
||||
@@ -40,6 +40,6 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "^5.8.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.4.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Features
|
||||
|
||||
* deprecated editors ([#1166](https://github.com/standardnotes/app/issues/1166)) ([60ca415](https://github.com/standardnotes/app/commit/60ca4150446f9a14bb6a31416686c6d07a7d0cd9))
|
||||
|
||||
## [1.3.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/classic-code-editor
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/classic-code-editor",
|
||||
"private": true,
|
||||
"version": "1.3.17",
|
||||
"version": "1.4.0",
|
||||
"description": "A code editor for Standard Notes",
|
||||
"main": "dist/main.js",
|
||||
"author": "Standard Notes <[email protected]>",
|
||||
@@ -30,6 +30,6 @@
|
||||
"sn-stylekit": "^2.1.1",
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.7.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/rich-text
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-29)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/rich-text",
|
||||
"private": true,
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1",
|
||||
"description": "A rich text editor for Standard Notes",
|
||||
"main": "dist/dist.js",
|
||||
"author": "Standard Notes <[email protected]>",
|
||||
@@ -36,7 +36,7 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"webpack-merge-and-include-globally": "^2.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ste:** revert border radius change ([b174493](https://github.com/standardnotes/app/commit/b174493b4ea3ca164cf5754ab791022b17b612ac))
|
||||
|
||||
## [1.4.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/simple-task-editor
|
||||
|
||||
## [1.4.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ body {
|
||||
left: 0;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 10px;
|
||||
border-radius: 1px;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);;
|
||||
border: 1px solid var(--sn-stylekit-contrast-border-color);
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/simple-task-editor",
|
||||
"private": true,
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.3",
|
||||
"main": "dist/dist.js",
|
||||
"scripts": {
|
||||
"components:compile": "webpack --config webpack.prod.js",
|
||||
@@ -43,6 +43,6 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "5.7.3"
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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.
|
||||
|
||||
## [1.5.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/spreadsheets
|
||||
|
||||
# [1.5.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/spreadsheets",
|
||||
"private": true,
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"main": "dist/dist.js",
|
||||
"scripts": {
|
||||
"skip:lint": "eslint app/ --ext .js",
|
||||
@@ -39,6 +39,6 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [2.2.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/authenticator
|
||||
|
||||
## [2.2.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/authenticator
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/authenticator",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"main": "dist/dist.js",
|
||||
"author": "Standard Notes",
|
||||
"private": true,
|
||||
@@ -58,6 +58,6 @@
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.23.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.23.73",
|
||||
"version": "3.23.79",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
@@ -79,7 +79,7 @@
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "*",
|
||||
"webpack": "*",
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"build": {
|
||||
"electronVersion": "17.4.2",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.4.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/icons
|
||||
|
||||
## [1.4.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/icons",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.7",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.00002 2.5L1.66669 7.5L10 18.3333L18.3334 7.5L15 2.5H5.00002Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 156 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.525 15L10 12.875L6.47502 15L7.40835 10.9916L4.30002 8.29996L8.40002 7.94996L10 4.16663L11.6 7.94163L15.7 8.29163L12.5917 10.9833L13.525 15ZM10 1.66663C5.39169 1.66663 1.66669 5.41663 1.66669 9.99996C1.66669 12.2101 2.54466 14.3297 4.10746 15.8925C4.88129 16.6663 5.79994 17.2802 6.81099 17.699C7.82204 18.1177 8.90567 18.3333 10 18.3333C12.2102 18.3333 14.3298 17.4553 15.8926 15.8925C17.4554 14.3297 18.3334 12.2101 18.3334 9.99996C18.3334 8.90561 18.1178 7.82198 17.699 6.81093C17.2802 5.79988 16.6664 4.88122 15.8926 4.1074C15.1188 3.33358 14.2001 2.71975 13.189 2.30096C12.178 1.88217 11.0944 1.66663 10 1.66663V1.66663Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 721 B |
@@ -47,6 +47,7 @@ import CopyIcon from './ic-copy.svg'
|
||||
import CreateAccountIllustration from './create-account-illustration.svg'
|
||||
import DashboardIcon from './ic-dashboard.svg'
|
||||
import DiamondIcon from './diamond-with-horizontal-lines.svg'
|
||||
import DiamondFilledIcon from './ic-diamond-filled.svg'
|
||||
import DownloadIcon from './ic-download.svg'
|
||||
import DragIcon from './ic-drag.svg'
|
||||
import DrawIcon from './ic-draw.svg'
|
||||
@@ -167,6 +168,7 @@ import SNLogoIcon from './ic-standard-notes.svg'
|
||||
import SortDescendingIcon from './ic-sort-descending.svg'
|
||||
import SpreadsheetsIcon from './ic-spreadsheets.svg'
|
||||
import StarFilledIcon from './ic-star-filled.svg'
|
||||
import StarCircleFilled from './ic-star-circle-filled.svg'
|
||||
import StarIcon from './ic-star.svg'
|
||||
import StarVariantFilledIcon from './ic-star-variant-filled.svg'
|
||||
import StrikethroughIcon from './ic-strikethrough.svg'
|
||||
@@ -245,6 +247,7 @@ export {
|
||||
CopyIcon,
|
||||
CreateAccountIllustration,
|
||||
DashboardIcon,
|
||||
DiamondFilledIcon,
|
||||
DiamondIcon,
|
||||
DownloadIcon,
|
||||
DragIcon,
|
||||
@@ -365,6 +368,7 @@ export {
|
||||
SNLogoIcon,
|
||||
SortDescendingIcon,
|
||||
SpreadsheetsIcon,
|
||||
StarCircleFilled,
|
||||
StarFilledIcon,
|
||||
StarIcon,
|
||||
StarVariantFilledIcon,
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.30.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.30.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -64,6 +64,8 @@ platform :ios do
|
||||
skip_metadata: true,
|
||||
skip_screenshots: true,
|
||||
skip_binary_upload: true,
|
||||
run_precheck_before_submit: false,
|
||||
submission_information: { add_id_info_uses_idfa: false },
|
||||
release_notes: {
|
||||
'default': 'Fixes and improvements.'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.30.28",
|
||||
"version": "3.30.35",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
# [1.3.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **releases:** add mobile urls ([98fc870](https://github.com/standardnotes/app/commit/98fc870d761b50dadc00ebb4e41abba987bbcfb0))
|
||||
|
||||
## [1.2.99](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.2.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.2.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.2.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.2.95](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.2.94](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.2.94",
|
||||
"version": "1.3.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -32,6 +32,9 @@ const Downloads = {
|
||||
WinIa32Exe: `${ReleaseUrl}-win-ia32.exe`,
|
||||
WinX64Exe: `${ReleaseUrl}-win-x64.exe`,
|
||||
WinExe: `${ReleaseUrl}-win.exe`,
|
||||
AppStoreiOS: 'https://itunes.apple.com/us/app/standard-notes/id1285392450?mt=8',
|
||||
GooglePlay: 'https://play.google.com/store/apps/details?id=com.standardnotes',
|
||||
WebApp: 'https://app.standardnotes.com',
|
||||
}
|
||||
|
||||
ensureDirExists('dist')
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.124.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.124.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.124.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.124.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# SNJS
|
||||
|
||||
SNJS is a client-side JavaScript library for [Standard Notes](https://standardnotes.com) that contains shared logic for all Standard Notes clients.
|
||||
|
||||
## Introduction
|
||||
|
||||
SNJS is a shared library for use in all Standard Notes clients (desktop, web, and mobile). Its role is to extract any business or data logic from client code, so that clients are mostly responsible for UI-level code, and don’t have to think about encryption and key management, or even authentication or storage specifics. Extracting the code into a shared library also prevents us from having to write the same critical code on multiple platforms.
|
||||
|
||||
The entry point of SNJS is the [`SNApplication`](packages/snjs/lib/application.ts) class. The application class is a complete unit of application functionality. Theoretically, many instances of an application can be created, each with its own storage namespace and memory state. This can allow clients to support multiple user accounts.
|
||||
|
||||
An application must be supplied a custom subclass of [DeviceInterface](packages/snjs/lib/device_interface.ts). This allows the library to generalize all behavior a client will need to perform throughout normal client operation, such as saving data to a local database store, saving key/values, and accessing the keychain.
|
||||
|
||||
On Web platforms SNJS interacts with [`sncrypto`](https://github.com/standardnotes/snjs/tree/packages/sncrypto-common) to perform operations as mentioned in the [specification](https://github.com/standardnotes/snjs/blob/main/packages/snjs/specification.md) document. This includes operations like key generation and data encryption.
|
||||
|
||||
SNJS also interacts with a Standard Notes [syncing server](https://github.com/standardnotes/syncing-server-js), which is a zero-knowledge data and sync store that deals with encrypted data, and never learns of client secrets or sensitive information.
|
||||
|
||||
## Installation
|
||||
|
||||
`yarn add snjs`
|
||||
|
||||
## Integrating in module environment
|
||||
|
||||
```javascript
|
||||
import { SNApplication } from 'snjs';
|
||||
```
|
||||
|
||||
## Integrating in non-module web environment
|
||||
|
||||
```javascript
|
||||
<script src="snjs.js"></script>
|
||||
Object.assign(window, SNLibrary);
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
1. `yarn install --pure-lockfile`
|
||||
2. `yarn start` to start Webpack in development mode (watches changes), or `yarn build` to create dist files.
|
||||
|
||||
## Tests
|
||||
|
||||
### E2E Tests
|
||||
|
||||
To run a stable server environment for E2E tests that is up to date with production, clone the [e2e repository](https://github.com/standardnotes/e2e), then run:
|
||||
|
||||
```
|
||||
yarn install --immutable
|
||||
yarn test:stable-server
|
||||
```
|
||||
|
||||
Once the server infrastructure is ready, and you've built all packages, you can run the test suite in the browser via:
|
||||
|
||||
```
|
||||
yarn start:server:e2e
|
||||
```
|
||||
|
||||
### Unit Tests
|
||||
|
||||
From the root of the repository, run:
|
||||
|
||||
```
|
||||
yarn run test:unit
|
||||
```
|
||||
@@ -40,6 +40,8 @@ export type IconType =
|
||||
| 'color-fill'
|
||||
| 'copy'
|
||||
| 'dashboard'
|
||||
| 'diamond-filled'
|
||||
| 'diamond'
|
||||
| 'download'
|
||||
| 'drag'
|
||||
| 'draw'
|
||||
@@ -151,6 +153,7 @@ export type IconType =
|
||||
| 'spreadsheets'
|
||||
| 'standard-notes-2'
|
||||
| 'standard-notes'
|
||||
| 'star-circle-filled'
|
||||
| 'star-filled'
|
||||
| 'star-variant-filled'
|
||||
| 'star'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.124.2",
|
||||
"version": "2.124.5",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -49,7 +49,7 @@
|
||||
"exports-loader": "^3.1.0",
|
||||
"jest": "^28.1.2",
|
||||
"jest-environment-jsdom": "^28.1.2",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"libsodium-wrappers": "^0.7.9",
|
||||
"lodash": "^4.17.21",
|
||||
"nock": "^13.2.4",
|
||||
@@ -63,7 +63,7 @@
|
||||
"uuid": "^8.3.2",
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:*",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.3.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
## [1.3.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/toast",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.102](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.101](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.100](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.99](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
## [1.2.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web-server",
|
||||
"version": "1.2.96",
|
||||
"version": "1.2.102",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": true,
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-20)
|
||||
|
||||
### Features
|
||||
|
||||
* responsive popovers & menus ([#1323](https://github.com/standardnotes/app/issues/1323)) ([2573407](https://github.com/standardnotes/app/commit/2573407851c1ee3e9392ce504f020db68b7856b2))
|
||||
|
||||
## [3.40.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.40.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.40.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **web:** prevent jumping the page contents on mobile when editing notes offline ([#1294](https://github.com/standardnotes/app/issues/1294)) ([aaf68a8](https://github.com/standardnotes/app/commit/aaf68a8f73cf52b1256e3ba879cc00bbcdcf251d))
|
||||
|
||||
## [3.40.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.40.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-07-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.40.3",
|
||||
"version": "3.41.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
@@ -60,7 +60,7 @@
|
||||
"typescript": "*",
|
||||
"webpack": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reach/alert": "^0.16.0",
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { useCallback, useRef, FunctionComponent, KeyboardEventHandler } from 'react'
|
||||
import { useCallback, FunctionComponent, KeyboardEventHandler } from 'react'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { AccountMenuPane } from './AccountMenuPane'
|
||||
import MenuPaneSelector from './MenuPaneSelector'
|
||||
|
||||
type Props = {
|
||||
export type AccountMenuProps = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
application: WebApplication
|
||||
onClickOutside: () => void
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
}
|
||||
|
||||
const AccountMenu: FunctionComponent<Props> = ({
|
||||
const AccountMenu: FunctionComponent<AccountMenuProps> = ({
|
||||
application,
|
||||
viewControllerManager,
|
||||
onClickOutside,
|
||||
mainApplicationGroup,
|
||||
}) => {
|
||||
const { currentPane, shouldAnimateCloseMenu } = viewControllerManager.accountMenuController
|
||||
const { currentPane } = viewControllerManager.accountMenuController
|
||||
|
||||
const closeAccountMenu = useCallback(() => {
|
||||
viewControllerManager.accountMenuController.closeAccountMenu()
|
||||
@@ -33,11 +31,6 @@ const AccountMenu: FunctionComponent<Props> = ({
|
||||
[viewControllerManager],
|
||||
)
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useCloseOnClickOutside(ref, () => {
|
||||
onClickOutside()
|
||||
})
|
||||
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = useCallback(
|
||||
(event) => {
|
||||
switch (event.key) {
|
||||
@@ -56,22 +49,15 @@ const AccountMenu: FunctionComponent<Props> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<div ref={ref} id="account-menu" className="sn-component">
|
||||
<div
|
||||
className={`max-h-120 absolute bottom-full left-0 z-footer-bar-item-panel flex min-w-80 max-w-xs cursor-auto flex-col overflow-y-auto rounded bg-default py-2 shadow-main ${
|
||||
shouldAnimateCloseMenu ? 'slide-up-animation' : 'slide-down-animation transition-transform duration-150'
|
||||
}`}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<MenuPaneSelector
|
||||
viewControllerManager={viewControllerManager}
|
||||
application={application}
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
menuPane={currentPane}
|
||||
setMenuPane={setCurrentPane}
|
||||
closeMenu={closeAccountMenu}
|
||||
/>
|
||||
</div>
|
||||
<div id="account-menu" className="sn-component" onKeyDown={handleKeyDown}>
|
||||
<MenuPaneSelector
|
||||
viewControllerManager={viewControllerManager}
|
||||
application={application}
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
menuPane={currentPane}
|
||||
setMenuPane={setCurrentPane}
|
||||
closeMenu={closeAccountMenu}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@ const WorkspaceSwitcherMenu: FunctionComponent<Props> = ({
|
||||
isOpen,
|
||||
hideWorkspaceOptions = false,
|
||||
}: Props) => {
|
||||
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>([])
|
||||
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>(
|
||||
mainApplicationGroup.getDescriptors(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const applicationDescriptors = mainApplicationGroup.getDescriptors()
|
||||
|
||||
+18
-38
@@ -1,13 +1,13 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import WorkspaceSwitcherMenu from './WorkspaceSwitcherMenu'
|
||||
import MenuItem from '@/Components/Menu/MenuItem'
|
||||
import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
||||
import Popover from '@/Components/Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
@@ -16,32 +16,11 @@ type Props = {
|
||||
|
||||
const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGroup, viewControllerManager }) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
|
||||
|
||||
const toggleMenu = useCallback(() => {
|
||||
if (!isOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(buttonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen)
|
||||
}, [isOpen, setIsOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTimeout(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [isOpen])
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -58,19 +37,20 @@ const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGrou
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</MenuItem>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="max-h-120 fixed min-w-68 overflow-y-auto rounded bg-default py-2 shadow-main"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Popover
|
||||
align="end"
|
||||
anchorElement={buttonRef.current}
|
||||
className="py-2"
|
||||
open={isOpen}
|
||||
side="right"
|
||||
togglePopover={toggleMenu}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+46
-91
@@ -1,11 +1,7 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import AttachedFilesPopover from './AttachedFilesPopover'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import { PopoverTabs } from './PopoverTabs'
|
||||
@@ -18,6 +14,8 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { useFileDragNDrop } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
|
||||
import { FileItem, SNNote } from '@standardnotes/snjs'
|
||||
import { addToast, ToastType } from '@standardnotes/toast'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -34,7 +32,6 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
application,
|
||||
featuresController,
|
||||
filesController,
|
||||
filePreviewModalController,
|
||||
navigationController,
|
||||
notesController,
|
||||
selectionController,
|
||||
@@ -46,24 +43,9 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
const premiumModal = usePremiumModal()
|
||||
const note: SNNote | undefined = notesController.firstSelectedNote
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [position, setPosition] = useState({
|
||||
top: 0,
|
||||
right: 0,
|
||||
})
|
||||
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const panelRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur, keepMenuOpen] = useCloseOnBlur(containerRef, setOpen)
|
||||
|
||||
useEffect(() => {
|
||||
if (filePreviewModalController.isOpen) {
|
||||
keepMenuOpen(true)
|
||||
} else {
|
||||
keepMenuOpen(false)
|
||||
}
|
||||
}, [filePreviewModalController.isOpen, keepMenuOpen])
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(
|
||||
navigationController.isInFilesView ? PopoverTabs.AllFiles : PopoverTabs.AttachedFiles,
|
||||
@@ -78,29 +60,14 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
}, [currentTab, isAttachedTabDisabled])
|
||||
|
||||
const toggleAttachedFilesMenu = useCallback(async () => {
|
||||
const rect = buttonRef.current?.getBoundingClientRect()
|
||||
if (rect) {
|
||||
const { clientHeight } = document.documentElement
|
||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||
const footerHeightInPx = footerElementRect?.height
|
||||
const newOpenState = !isOpen
|
||||
|
||||
if (footerHeightInPx) {
|
||||
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - MENU_MARGIN_FROM_APP_BORDER)
|
||||
}
|
||||
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
})
|
||||
|
||||
const newOpenState = !open
|
||||
if (newOpenState && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
|
||||
setOpen(newOpenState)
|
||||
if (newOpenState && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
}, [onClickPreprocessing, open])
|
||||
|
||||
setIsOpen(newOpenState)
|
||||
}, [onClickPreprocessing, isOpen])
|
||||
|
||||
const prospectivelyShowFilesPremiumModal = useCallback(() => {
|
||||
if (!featuresController.hasFiles) {
|
||||
@@ -132,10 +99,10 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
const { isDraggingFiles, addFilesDragInCallback, addFilesDropCallback } = useFileDragNDrop()
|
||||
|
||||
useEffect(() => {
|
||||
if (isDraggingFiles && !open) {
|
||||
if (isDraggingFiles && !isOpen) {
|
||||
void toggleAttachedFilesMenu()
|
||||
}
|
||||
}, [isDraggingFiles, open, toggleAttachedFilesMenu])
|
||||
}, [isDraggingFiles, isOpen, toggleAttachedFilesMenu])
|
||||
|
||||
const filesDragInCallback = useCallback((tab: PopoverTabs) => {
|
||||
setCurrentTab(tab)
|
||||
@@ -162,53 +129,41 @@ const AttachedFilesButton: FunctionComponent<Props> = ({
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<Disclosure open={open} onChange={toggleAttachedFilesMenuWithEntitlementCheck}>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
className={`bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast ${
|
||||
attachedFilesCount > 0 ? 'py-1 px-3' : ''
|
||||
}`}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<VisuallyHidden>Attached files</VisuallyHidden>
|
||||
<Icon type="attachment-file" className="block" />
|
||||
{attachedFilesCount > 0 && <span className="ml-2 text-sm">{attachedFilesCount}</span>}
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
buttonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="slide-down-animation max-h-120 fixed flex min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default shadow-main transition-transform duration-150"
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
{open && (
|
||||
<AttachedFilesPopover
|
||||
application={application}
|
||||
filesController={filesController}
|
||||
attachedFiles={attachedFiles}
|
||||
allFiles={allFiles}
|
||||
closeOnBlur={closeOnBlur}
|
||||
currentTab={currentTab}
|
||||
isDraggingFiles={isDraggingFiles}
|
||||
setCurrentTab={setCurrentTab}
|
||||
attachedTabDisabled={isAttachedTabDisabled}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<button
|
||||
className={classNames(
|
||||
'bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast',
|
||||
attachedFilesCount > 0 ? 'py-1 px-3' : '',
|
||||
)}
|
||||
title="Attached files"
|
||||
aria-label="Attached files"
|
||||
onClick={toggleAttachedFilesMenuWithEntitlementCheck}
|
||||
ref={buttonRef}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon type="attachment-file" />
|
||||
{attachedFilesCount > 0 && <span className="ml-2 text-sm">{attachedFilesCount}</span>}
|
||||
</button>
|
||||
<Popover
|
||||
togglePopover={toggleAttachedFilesMenuWithEntitlementCheck}
|
||||
anchorElement={buttonRef.current}
|
||||
open={isOpen}
|
||||
className="pt-2 md:pt-0"
|
||||
>
|
||||
<AttachedFilesPopover
|
||||
application={application}
|
||||
filesController={filesController}
|
||||
attachedFiles={attachedFiles}
|
||||
allFiles={allFiles}
|
||||
currentTab={currentTab}
|
||||
isDraggingFiles={isDraggingFiles}
|
||||
setCurrentTab={setCurrentTab}
|
||||
attachedTabDisabled={isAttachedTabDisabled}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-9
@@ -16,7 +16,6 @@ type Props = {
|
||||
filesController: FilesController
|
||||
allFiles: FileItem[]
|
||||
attachedFiles: FileItem[]
|
||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||
currentTab: PopoverTabs
|
||||
isDraggingFiles: boolean
|
||||
setCurrentTab: Dispatch<SetStateAction<PopoverTabs>>
|
||||
@@ -28,7 +27,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
filesController,
|
||||
allFiles,
|
||||
attachedFiles,
|
||||
closeOnBlur,
|
||||
currentTab,
|
||||
isDraggingFiles,
|
||||
setCurrentTab,
|
||||
@@ -87,7 +85,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
onClick={() => {
|
||||
setCurrentTab(PopoverTabs.AttachedFiles)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
disabled={attachedTabDisabled}
|
||||
>
|
||||
Attached
|
||||
@@ -100,7 +97,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
onClick={() => {
|
||||
setCurrentTab(PopoverTabs.AllFiles)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
All files
|
||||
</button>
|
||||
@@ -117,7 +113,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
onInput={(e) => {
|
||||
setSearchQuery((e.target as HTMLInputElement).value)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={searchInputRef}
|
||||
/>
|
||||
{searchQuery.length > 0 && (
|
||||
@@ -127,7 +122,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
setSearchQuery('')
|
||||
searchInputRef.current?.focus()
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Icon type="clear-circle-filled" className="text-neutral" />
|
||||
</button>
|
||||
@@ -144,7 +138,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
isAttachedToNote={attachedFiles.includes(file)}
|
||||
handleFileAction={filesController.handleFileAction}
|
||||
getIconType={application.iconsController.getIconForFileType}
|
||||
closeOnBlur={closeOnBlur}
|
||||
previewHandler={previewHandler}
|
||||
/>
|
||||
)
|
||||
@@ -161,7 +154,7 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
? 'No files attached to this note'
|
||||
: 'No files found in this account'}
|
||||
</div>
|
||||
<Button onClick={handleAttachFilesClick} onBlur={closeOnBlur}>
|
||||
<Button onClick={handleAttachFilesClick}>
|
||||
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
|
||||
</Button>
|
||||
<div className="mt-3 text-xs text-passive-0">Or drop your files here</div>
|
||||
@@ -172,7 +165,6 @@ const AttachedFilesPopover: FunctionComponent<Props> = ({
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 border-t border-solid border-border bg-transparent px-3 py-3 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={handleAttachFilesClick}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Icon type="add" className="mr-2 text-neutral" />
|
||||
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'} files
|
||||
|
||||
@@ -22,7 +22,6 @@ const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
||||
isAttachedToNote,
|
||||
handleFileAction,
|
||||
getIconType,
|
||||
closeOnBlur,
|
||||
previewHandler,
|
||||
}) => {
|
||||
const [fileName, setFileName] = useState(file.name)
|
||||
@@ -116,7 +115,6 @@ const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
||||
isAttachedToNote={isAttachedToNote}
|
||||
handleFileAction={handleFileAction}
|
||||
setIsRenamingFile={setIsRenamingFile}
|
||||
closeOnBlur={closeOnBlur}
|
||||
previewHandler={previewHandler}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ type CommonProps = {
|
||||
handleFileAction: (action: PopoverFileItemAction) => Promise<{
|
||||
didHandleAction: boolean
|
||||
}>
|
||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||
previewHandler: (file: FileItem) => void
|
||||
}
|
||||
|
||||
|
||||
+118
-172
@@ -1,14 +1,12 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { PopoverFileSubmenuProps } from './PopoverFileItemProps'
|
||||
import { PopoverFileItemActionType } from './PopoverFileItemAction'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
file,
|
||||
@@ -19,187 +17,135 @@ const PopoverFileSubmenu: FunctionComponent<PopoverFileSubmenuProps> = ({
|
||||
}) => {
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isFileProtected, setIsFileProtected] = useState(file.protected)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
maxHeight: 'auto',
|
||||
})
|
||||
const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen)
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
setIsMenuOpen(false)
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
const toggleMenu = useCallback(() => {
|
||||
if (!isMenuOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(menuButtonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsMenuOpen(!isMenuOpen)
|
||||
}, [isMenuOpen])
|
||||
|
||||
const recalculateMenuStyle = useCallback(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(menuButtonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}, [isMenuOpen, recalculateMenuStyle])
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<Disclosure open={isMenuOpen} onChange={toggleMenu}>
|
||||
<DisclosureButton
|
||||
ref={menuButtonRef}
|
||||
onBlur={closeOnBlur}
|
||||
className="h-7 w-7 cursor-pointer rounded-full border-0 bg-transparent p-1 hover:bg-contrast"
|
||||
>
|
||||
<Icon type="more" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
style={{
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
<button
|
||||
ref={menuButtonRef}
|
||||
onClick={toggleMenu}
|
||||
className="h-7 w-7 cursor-pointer rounded-full border-0 bg-transparent p-1 hover:bg-contrast"
|
||||
>
|
||||
<Icon type="more" className="text-neutral" />
|
||||
</button>
|
||||
<Popover anchorElement={menuButtonRef.current} open={isOpen} togglePopover={toggleMenu} className="py-2">
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
previewHandler(file)
|
||||
closeMenu()
|
||||
}}
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} max-h-120 fixed min-w-60 flex-col overflow-y-auto rounded bg-default py-1 shadow-main`}
|
||||
>
|
||||
{isMenuOpen && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
previewHandler(file)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="file" className="mr-2 text-neutral" />
|
||||
Preview file
|
||||
</button>
|
||||
{isAttachedToNote ? (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DetachFileToNote,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link-off" className="mr-2 text-neutral" />
|
||||
Detach from note
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.AttachFileToNote,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link" className="mr-2 text-neutral" />
|
||||
Attach to note
|
||||
</button>
|
||||
)}
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.ToggleFileProtection,
|
||||
payload: { file },
|
||||
callback: (isProtected: boolean) => {
|
||||
setIsFileProtected(isProtected)
|
||||
},
|
||||
}).catch(console.error)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className="mr-2 text-neutral" />
|
||||
Password protection
|
||||
</span>
|
||||
<Switch
|
||||
className="pointer-events-none px-0"
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
checked={isFileProtected}
|
||||
/>
|
||||
</button>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DownloadFile,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="download" className="mr-2 text-neutral" />
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
setIsRenamingFile(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="mr-2 text-neutral" />
|
||||
Rename
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DeleteFile,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</button>
|
||||
<div className="px-3 py-1 text-xs font-medium text-neutral">
|
||||
<div className="mb-1">
|
||||
<span className="font-semibold">File ID:</span> {file.uuid}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Size:</span> {formatSizeToReadableString(file.decryptedSize)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<Icon type="file" className="mr-2 text-neutral" />
|
||||
Preview file
|
||||
</button>
|
||||
{isAttachedToNote ? (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DetachFileToNote,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link-off" className="mr-2 text-neutral" />
|
||||
Detach from note
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.AttachFileToNote,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="link" className="mr-2 text-neutral" />
|
||||
Attach to note
|
||||
</button>
|
||||
)}
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.ToggleFileProtection,
|
||||
payload: { file },
|
||||
callback: (isProtected: boolean) => {
|
||||
setIsFileProtected(isProtected)
|
||||
},
|
||||
}).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className="mr-2 text-neutral" />
|
||||
Password protection
|
||||
</span>
|
||||
<Switch
|
||||
className="pointer-events-none px-0"
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
checked={isFileProtected}
|
||||
/>
|
||||
</button>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DownloadFile,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="download" className="mr-2 text-neutral" />
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
setIsRenamingFile(true)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="mr-2 text-neutral" />
|
||||
Rename
|
||||
</button>
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
handleFileAction({
|
||||
type: PopoverFileItemActionType.DeleteFile,
|
||||
payload: { file },
|
||||
}).catch(console.error)
|
||||
closeMenu()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="mr-2 text-danger" />
|
||||
<span className="text-danger">Delete permanently</span>
|
||||
</button>
|
||||
<div className="px-3 py-1 text-xs font-medium text-neutral">
|
||||
<div className="mb-1">
|
||||
<span className="font-semibold">File ID:</span> {file.uuid}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Size:</span> {formatSizeToReadableString(file.decryptedSize)}
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+20
-43
@@ -1,11 +1,10 @@
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import WorkspaceSwitcherMenu from '@/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
@@ -14,36 +13,12 @@ type Props = {
|
||||
|
||||
const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({ mainApplicationGroup, viewControllerManager }) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
|
||||
|
||||
useCloseOnClickOutside(containerRef, () => setIsOpen(false))
|
||||
|
||||
const toggleMenu = useCallback(() => {
|
||||
if (!isOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(buttonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen)
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const timeToWaitBeforeCheckingMenuCollision = 0
|
||||
setTimeout(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
}, timeToWaitBeforeCheckingMenuCollision)
|
||||
}
|
||||
}, [isOpen])
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
@@ -51,20 +26,22 @@ const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({ mainApplication
|
||||
<Icon type="user-switch" className="mr-2 text-neutral" />
|
||||
Switch workspace
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="max-h-120 fixed min-w-68 overflow-y-auto rounded-md bg-default py-2 shadow-main"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
isOpen={isOpen}
|
||||
hideWorkspaceOptions={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Popover
|
||||
align="center"
|
||||
anchorElement={buttonRef.current}
|
||||
className="py-2"
|
||||
open={isOpen}
|
||||
overrideZIndex="z-modal"
|
||||
side="right"
|
||||
togglePopover={toggleMenu}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
viewControllerManager={viewControllerManager}
|
||||
isOpen={isOpen}
|
||||
hideWorkspaceOptions={true}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ChangeEditorMenu from './ChangeEditorMenu'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -22,89 +19,38 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
||||
}: Props) => {
|
||||
const note = viewControllerManager.notesController.firstSelectedNote
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [position, setPosition] = useState({
|
||||
top: 0,
|
||||
right: 0,
|
||||
})
|
||||
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const panelRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur] = useCloseOnBlur(containerRef, setIsOpen)
|
||||
|
||||
const toggleChangeEditorMenu = async () => {
|
||||
const rect = buttonRef.current?.getBoundingClientRect()
|
||||
if (rect) {
|
||||
const { clientHeight } = document.documentElement
|
||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||
const footerHeightInPx = footerElementRect?.height
|
||||
|
||||
if (footerHeightInPx) {
|
||||
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - MENU_MARGIN_FROM_APP_BORDER)
|
||||
}
|
||||
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
})
|
||||
|
||||
const newOpenState = !isOpen
|
||||
if (newOpenState && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
|
||||
setIsOpen(newOpenState)
|
||||
setTimeout(() => {
|
||||
setIsVisible(newOpenState)
|
||||
})
|
||||
const toggleMenu = useCallback(async () => {
|
||||
const willMenuOpen = !isOpen
|
||||
if (willMenuOpen && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
}
|
||||
setIsOpen(willMenuOpen)
|
||||
}, [onClickPreprocessing, isOpen])
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<Disclosure open={isOpen} onChange={toggleChangeEditorMenu}>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsOpen(false)
|
||||
}
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="Change note type"
|
||||
aria-label="Change note type"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<Icon type="dashboard" />
|
||||
</button>
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="pt-2 md:pt-0">
|
||||
<ChangeEditorMenu
|
||||
application={application}
|
||||
isVisible={isOpen}
|
||||
note={note}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
>
|
||||
<VisuallyHidden>Change note type</VisuallyHidden>
|
||||
<Icon type="dashboard" className="block" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsOpen(false)
|
||||
buttonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className="slide-down-animation max-h-120 fixed flex min-w-68 max-w-xs flex-col overflow-y-auto rounded bg-default shadow-main transition-transform duration-150"
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
{isOpen && (
|
||||
<ChangeEditorMenu
|
||||
closeOnBlur={closeOnBlur}
|
||||
application={application}
|
||||
isVisible={isVisible}
|
||||
note={note}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
SNNote,
|
||||
TransactionalMutation,
|
||||
} from '@standardnotes/snjs'
|
||||
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
import { Fragment, FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
|
||||
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
|
||||
import { createEditorMenuGroups } from './createEditorMenuGroups'
|
||||
@@ -24,10 +24,10 @@ import {
|
||||
transactionForDisassociateComponentWithCurrentNote,
|
||||
} from '../NoteView/TransactionFunctions'
|
||||
import { reloadFont } from '../NoteView/FontFunctions'
|
||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
|
||||
type ChangeEditorMenuProps = {
|
||||
application: WebApplication
|
||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||
closeMenu: () => void
|
||||
isVisible: boolean
|
||||
note: SNNote | undefined
|
||||
@@ -35,25 +35,17 @@ type ChangeEditorMenuProps = {
|
||||
|
||||
const getGroupId = (group: EditorMenuGroup) => group.title.toLowerCase().replace(/\s/, '-')
|
||||
|
||||
const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
application,
|
||||
closeOnBlur,
|
||||
closeMenu,
|
||||
isVisible,
|
||||
note,
|
||||
}) => {
|
||||
const [editors] = useState<SNComponent[]>(() =>
|
||||
application.componentManager.componentsForArea(ComponentArea.Editor).sort((a, b) => {
|
||||
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1
|
||||
}),
|
||||
const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({ application, closeMenu, isVisible, note }) => {
|
||||
const editors = useMemo(
|
||||
() =>
|
||||
application.componentManager.componentsForArea(ComponentArea.Editor).sort((a, b) => {
|
||||
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1
|
||||
}),
|
||||
[application.componentManager],
|
||||
)
|
||||
const [groups, setGroups] = useState<EditorMenuGroup[]>([])
|
||||
const groups = useMemo(() => createEditorMenuGroups(application, editors), [application, editors])
|
||||
const [currentEditor, setCurrentEditor] = useState<SNComponent>()
|
||||
|
||||
useEffect(() => {
|
||||
setGroups(createEditorMenuGroups(application, editors))
|
||||
}, [application, editors])
|
||||
|
||||
useEffect(() => {
|
||||
if (note) {
|
||||
setCurrentEditor(application.componentManager.editorForNote(note))
|
||||
@@ -194,7 +186,6 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
type={MenuItemType.RadioButton}
|
||||
onClick={onClickEditorItem}
|
||||
className={'flex-row-reverse py-2'}
|
||||
onBlur={closeOnBlur}
|
||||
checked={item.isEntitled ? isSelectedEditor(item) : undefined}
|
||||
>
|
||||
<div className="flex flex-grow items-center justify-between">
|
||||
@@ -202,7 +193,7 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
{group.icon && <Icon type={group.icon} className={`mr-2 ${group.iconClassName}`} />}
|
||||
{item.name}
|
||||
</div>
|
||||
{!item.isEntitled && <Icon type="premium-feature" />}
|
||||
{!item.isEntitled && <Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)
|
||||
|
||||
+27
-31
@@ -1,10 +1,9 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Disclosure, DisclosurePanel } from '@reach/disclosure'
|
||||
import { memo, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '../../Icon/Icon'
|
||||
import { DisplayOptionsMenuPositionProps } from './DisplayOptionsMenuProps'
|
||||
import DisplayOptionsMenuPortal from './DisplayOptionsMenuPortal'
|
||||
import StyledDisplayOptionsButton from './StyledDisplayOptionsButton'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import Popover from '@/Components/Popover/Popover'
|
||||
import DisplayOptionsMenu from './DisplayOptionsMenu'
|
||||
|
||||
type Props = {
|
||||
application: {
|
||||
@@ -26,21 +25,12 @@ const ContentListHeader = ({
|
||||
isFilesSmartView,
|
||||
optionsSubtitle,
|
||||
}: Props) => {
|
||||
const [displayOptionsMenuPosition, setDisplayOptionsMenuPosition] = useState<DisplayOptionsMenuPositionProps>()
|
||||
const displayOptionsContainerRef = useRef<HTMLDivElement>(null)
|
||||
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
||||
|
||||
const toggleDisplayOptionsMenu = useCallback(() => {
|
||||
if (displayOptionsButtonRef.current) {
|
||||
const buttonBoundingRect = displayOptionsButtonRef.current.getBoundingClientRect()
|
||||
setDisplayOptionsMenuPosition({
|
||||
top: buttonBoundingRect.bottom,
|
||||
left: buttonBoundingRect.right - buttonBoundingRect.width,
|
||||
})
|
||||
}
|
||||
|
||||
setShowDisplayOptionsMenu((show) => !show)
|
||||
}, [])
|
||||
|
||||
@@ -52,24 +42,30 @@ const ContentListHeader = ({
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="relative" ref={displayOptionsContainerRef}>
|
||||
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
|
||||
<StyledDisplayOptionsButton $pressed={showDisplayOptionsMenu} ref={displayOptionsButtonRef}>
|
||||
<Icon type="sort-descending" />
|
||||
</StyledDisplayOptionsButton>
|
||||
<DisclosurePanel>
|
||||
{showDisplayOptionsMenu && displayOptionsMenuPosition && (
|
||||
<DisplayOptionsMenuPortal
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
||||
containerRef={displayOptionsContainerRef}
|
||||
isOpen={showDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
top={displayOptionsMenuPosition.top}
|
||||
left={displayOptionsMenuPosition.left}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<button
|
||||
className={classNames(
|
||||
'bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast',
|
||||
showDisplayOptionsMenu && 'bg-contrast',
|
||||
)}
|
||||
onClick={toggleDisplayOptionsMenu}
|
||||
ref={displayOptionsButtonRef}
|
||||
>
|
||||
<Icon type="sort-descending" />
|
||||
</button>
|
||||
<Popover
|
||||
open={showDisplayOptionsMenu}
|
||||
anchorElement={displayOptionsButtonRef.current}
|
||||
togglePopover={toggleDisplayOptionsMenu}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<DisplayOptionsMenu
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isOpen={showDisplayOptionsMenu}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
<button
|
||||
className="ml-3 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-transparent bg-info text-info-contrast hover:brightness-125"
|
||||
|
||||
+1
-8
@@ -97,14 +97,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
}, [application, hideEditorIcon])
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={
|
||||
'slide-down-animation z-index-dropdown-menu flex min-w-70 flex-col overflow-y-auto rounded border border-solid border-border bg-default py-1 text-sm shadow-main transition-transform duration-150'
|
||||
}
|
||||
a11yLabel="Notes list options menu"
|
||||
closeMenu={closeDisplayOptionsMenu}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<Menu className="text-sm" a11yLabel="Notes list options menu" closeMenu={closeDisplayOptionsMenu} isOpen={isOpen}>
|
||||
<div className="my-1 px-3 text-xs font-semibold uppercase text-text">Sort by</div>
|
||||
<MenuItem
|
||||
className="py-2"
|
||||
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import styled from 'styled-components'
|
||||
import { DisplayOptionsMenuPositionProps, DisplayOptionsMenuProps } from './DisplayOptionsMenuProps'
|
||||
import DisplayOptionsMenu from './DisplayOptionsMenu'
|
||||
import { useRef, useEffect, RefObject } from 'react'
|
||||
|
||||
type Props = DisplayOptionsMenuProps &
|
||||
DisplayOptionsMenuPositionProps & {
|
||||
containerRef: RefObject<HTMLDivElement>
|
||||
}
|
||||
|
||||
const PositionedOptionsMenu = styled.div<DisplayOptionsMenuPositionProps>`
|
||||
position: absolute;
|
||||
top: ${(props) => props.top}px;
|
||||
left: ${(props) => props.left}px;
|
||||
z-index: var(--z-index-dropdown-menu);
|
||||
`
|
||||
|
||||
const DisplayOptionsMenuPortal = ({
|
||||
application,
|
||||
closeDisplayOptionsMenu,
|
||||
containerRef,
|
||||
isFilesSmartView,
|
||||
isOpen,
|
||||
top,
|
||||
left,
|
||||
}: Props) => {
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const closeIfClickedOutside = (event: MouseEvent) => {
|
||||
const isDescendantOfMenu = menuRef.current?.contains(event.target as Node)
|
||||
const isDescendantOfContainer = containerRef.current?.contains(event.target as Node)
|
||||
|
||||
if (!isDescendantOfMenu && !isDescendantOfContainer) {
|
||||
closeDisplayOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', closeIfClickedOutside, { capture: true })
|
||||
return () => {
|
||||
document.removeEventListener('click', closeIfClickedOutside, {
|
||||
capture: true,
|
||||
})
|
||||
}
|
||||
}, [closeDisplayOptionsMenu, containerRef])
|
||||
|
||||
return createPortal(
|
||||
<PositionedOptionsMenu top={top} left={left} ref={menuRef}>
|
||||
<div className="sn-component">
|
||||
<DisplayOptionsMenu
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={closeDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
</PositionedOptionsMenu>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
export default DisplayOptionsMenuPortal
|
||||
@@ -1,10 +1,8 @@
|
||||
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useRef } from 'react'
|
||||
import Popover from '../Popover/Popover'
|
||||
import FileMenuOptions from './FileMenuOptions'
|
||||
|
||||
type Props = {
|
||||
@@ -15,92 +13,27 @@ type Props = {
|
||||
const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, selectionController }) => {
|
||||
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
|
||||
|
||||
const [contextMenuStyle, setContextMenuStyle] = useState<React.CSSProperties>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
visibility: 'hidden',
|
||||
})
|
||||
const [contextMenuMaxHeight, setContextMenuMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => setShowFileContextMenu(open))
|
||||
useCloseOnClickOutside(contextMenuRef, () => filesController.setShowFileContextMenu(false))
|
||||
|
||||
const reloadContextMenuLayout = useCallback(() => {
|
||||
const { clientHeight } = document.documentElement
|
||||
const defaultFontSize = window.getComputedStyle(document.documentElement).fontSize
|
||||
const maxContextMenuHeight = parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER
|
||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||
const footerHeightInPx = footerElementRect?.height
|
||||
|
||||
let openUpBottom = true
|
||||
|
||||
if (footerHeightInPx) {
|
||||
const bottomSpace = clientHeight - footerHeightInPx - fileContextMenuLocation.y
|
||||
const upSpace = fileContextMenuLocation.y
|
||||
|
||||
if (maxContextMenuHeight > bottomSpace) {
|
||||
if (upSpace > maxContextMenuHeight) {
|
||||
openUpBottom = false
|
||||
setContextMenuMaxHeight('auto')
|
||||
} else {
|
||||
if (upSpace > bottomSpace) {
|
||||
setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER)
|
||||
openUpBottom = false
|
||||
} else {
|
||||
setContextMenuMaxHeight(bottomSpace - MENU_MARGIN_FROM_APP_BORDER)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setContextMenuMaxHeight('auto')
|
||||
}
|
||||
}
|
||||
|
||||
if (openUpBottom) {
|
||||
setContextMenuStyle({
|
||||
top: fileContextMenuLocation.y,
|
||||
left: fileContextMenuLocation.x,
|
||||
visibility: 'visible',
|
||||
})
|
||||
} else {
|
||||
setContextMenuStyle({
|
||||
bottom: clientHeight - fileContextMenuLocation.y,
|
||||
left: fileContextMenuLocation.x,
|
||||
visibility: 'visible',
|
||||
})
|
||||
}
|
||||
}, [fileContextMenuLocation.x, fileContextMenuLocation.y])
|
||||
|
||||
useEffect(() => {
|
||||
if (showFileContextMenu) {
|
||||
reloadContextMenuLayout()
|
||||
}
|
||||
}, [reloadContextMenuLayout, showFileContextMenu])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', reloadContextMenuLayout)
|
||||
return () => {
|
||||
window.removeEventListener('resize', reloadContextMenuLayout)
|
||||
}
|
||||
}, [reloadContextMenuLayout])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className="max-h-120 fixed z-dropdown-menu flex min-w-60 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main"
|
||||
style={{
|
||||
...contextMenuStyle,
|
||||
maxHeight: contextMenuMaxHeight,
|
||||
}}
|
||||
<Popover
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
side="right"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
closeOnBlur={closeOnBlur}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</div>
|
||||
<div ref={contextMenuRef}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
|
||||
type Props = {
|
||||
closeMenu: () => void
|
||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
isFileAttachedToNote?: boolean
|
||||
@@ -22,7 +21,6 @@ type Props = {
|
||||
|
||||
const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
closeMenu,
|
||||
closeOnBlur,
|
||||
filesController,
|
||||
selectionController,
|
||||
isFileAttachedToNote,
|
||||
@@ -73,7 +71,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onPreview}
|
||||
>
|
||||
@@ -84,7 +81,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
<>
|
||||
{isFileAttachedToNote ? (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onDetach}
|
||||
>
|
||||
@@ -93,7 +89,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
</button>
|
||||
) : shouldShowAttachOption ? (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onAttach}
|
||||
>
|
||||
@@ -109,7 +104,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
onClick={() => {
|
||||
void filesController.setProtectionForFiles(!hasProtectedFiles, selectionController.selectedFiles)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className="mr-2 text-neutral" />
|
||||
@@ -123,7 +117,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
</button>
|
||||
<HorizontalSeparator classes="my-1" />
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
void filesController.downloadFiles(selectionController.selectedFiles)
|
||||
@@ -134,7 +127,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
</button>
|
||||
{shouldShowRenameOption && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
renameToggleCallback?.(true)
|
||||
@@ -145,7 +137,6 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
void filesController.deleteFilesPermanently(selectionController.selectedFiles)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import FileMenuOptions from './FileMenuOptions'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
@@ -15,80 +12,34 @@ type Props = {
|
||||
}
|
||||
|
||||
const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [position, setPosition] = useState({
|
||||
top: 0,
|
||||
right: 0,
|
||||
})
|
||||
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const panelRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
|
||||
|
||||
const onDisclosureChange = useCallback(async () => {
|
||||
const rect = buttonRef.current?.getBoundingClientRect()
|
||||
if (rect) {
|
||||
const { clientHeight } = document.documentElement
|
||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||
const footerHeightInPx = footerElementRect?.height
|
||||
if (footerHeightInPx) {
|
||||
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
|
||||
}
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
})
|
||||
setOpen((open) => !open)
|
||||
}
|
||||
}, [])
|
||||
const toggleMenu = useCallback(() => setIsOpen((isOpen) => !isOpen), [])
|
||||
|
||||
return (
|
||||
<Disclosure open={open} onChange={onDisclosureChange}>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
<>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="File options menu"
|
||||
aria-label="File options menu"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<VisuallyHidden>Actions</VisuallyHidden>
|
||||
<Icon type="more" className="block" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
buttonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className={`${
|
||||
open ? 'flex' : 'hidden'
|
||||
} max-h-120 slide-down-animation fixed min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main transition-transform duration-150`}
|
||||
onBlur={closeOnBlur}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
{open && (
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
closeOnBlur={closeOnBlur}
|
||||
closeMenu={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
shouldShowAttachOption={false}
|
||||
shouldShowRenameOption={false}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<Icon type="more" />
|
||||
</button>
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="py-2">
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
shouldShowAttachOption={false}
|
||||
shouldShowRenameOption={false}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { useRef } from 'react'
|
||||
import AccountMenu, { AccountMenuProps } from '../AccountMenu/AccountMenu'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = AccountMenuProps & {
|
||||
isOpen: boolean
|
||||
hasError: boolean
|
||||
toggleMenu: () => void
|
||||
user: unknown
|
||||
}
|
||||
|
||||
const AccountMenuButton = ({
|
||||
application,
|
||||
hasError,
|
||||
isOpen,
|
||||
mainApplicationGroup,
|
||||
onClickOutside,
|
||||
toggleMenu,
|
||||
user,
|
||||
viewControllerManager,
|
||||
}: Props) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={toggleMenu}
|
||||
className={classNames(
|
||||
isOpen ? 'bg-border' : '',
|
||||
'flex h-full w-8 cursor-pointer items-center justify-center rounded-full',
|
||||
)}
|
||||
>
|
||||
<div className={hasError ? 'text-danger' : (user ? 'text-info' : 'text-neutral') + ' h-5 w-5'}>
|
||||
<Icon type="account-circle" className="max-h-5 hover:text-info" />
|
||||
</div>
|
||||
</button>
|
||||
<Popover anchorElement={buttonRef.current} open={isOpen} togglePopover={toggleMenu} side="top" className="py-2">
|
||||
<AccountMenu
|
||||
onClickOutside={onClickOutside}
|
||||
viewControllerManager={viewControllerManager}
|
||||
application={application}
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountMenuButton
|
||||
@@ -12,13 +12,13 @@ import {
|
||||
STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON,
|
||||
} from '@/Constants/Strings'
|
||||
import { alertDialog, confirmDialog } from '@/Services/AlertService'
|
||||
import AccountMenu from '@/Components/AccountMenu/AccountMenu'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import QuickSettingsMenu from '@/Components/QuickSettingsMenu/QuickSettingsMenu'
|
||||
import SyncResolutionMenu from '@/Components/SyncResolutionMenu/SyncResolutionMenu'
|
||||
import { Fragment } from 'react'
|
||||
import { AccountMenuPane } from '../AccountMenu/AccountMenuPane'
|
||||
import { EditorEventSource } from '@/Types/EditorEventSource'
|
||||
import QuickSettingsButton from './QuickSettingsButton'
|
||||
import AccountMenuButton from './AccountMenuButton'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -287,12 +287,10 @@ class Footer extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
accountMenuClickHandler = () => {
|
||||
this.viewControllerManager.quickSettingsMenuController.closeQuickSettingsMenu()
|
||||
this.viewControllerManager.accountMenuController.toggleShow()
|
||||
}
|
||||
|
||||
quickSettingsClickHandler = () => {
|
||||
this.viewControllerManager.accountMenuController.closeAccountMenu()
|
||||
this.viewControllerManager.quickSettingsMenuController.toggle()
|
||||
}
|
||||
|
||||
@@ -342,55 +340,31 @@ class Footer extends PureComponent<Props, State> {
|
||||
override render() {
|
||||
return (
|
||||
<div className="sn-component">
|
||||
<div
|
||||
<footer
|
||||
id="footer-bar"
|
||||
className="z-footer-bar flex h-6 w-full select-none items-center justify-between border-t border-border bg-contrast px-3 text-text"
|
||||
>
|
||||
<div className="left flex h-full">
|
||||
<div className="sk-app-bar-item relative z-footer-bar-item ml-0 select-none">
|
||||
<div
|
||||
onClick={this.accountMenuClickHandler}
|
||||
className={
|
||||
(this.state.showAccountMenu ? 'bg-border' : '') +
|
||||
' flex h-full w-8 cursor-pointer items-center justify-center rounded-full'
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
this.state.hasError ? 'text-danger' : (this.user ? 'text-info' : 'text-neutral') + ' h-5 w-5'
|
||||
}
|
||||
>
|
||||
<Icon type="account-circle" className="max-h-5 hover:text-info" />
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showAccountMenu && (
|
||||
<AccountMenu
|
||||
onClickOutside={this.clickOutsideAccountMenu}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
application={this.application}
|
||||
mainApplicationGroup={this.props.applicationGroup}
|
||||
/>
|
||||
)}
|
||||
<AccountMenuButton
|
||||
application={this.application}
|
||||
hasError={this.state.hasError}
|
||||
isOpen={this.state.showAccountMenu}
|
||||
mainApplicationGroup={this.props.applicationGroup}
|
||||
onClickOutside={this.clickOutsideAccountMenu}
|
||||
toggleMenu={this.accountMenuClickHandler}
|
||||
user={this.user}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
/>
|
||||
</div>
|
||||
<div className="sk-app-bar-item ml-0-important relative z-footer-bar-item select-none">
|
||||
<div
|
||||
onClick={this.quickSettingsClickHandler}
|
||||
className="flex h-full w-8 cursor-pointer items-center justify-center"
|
||||
>
|
||||
<div className="h-5">
|
||||
<Icon
|
||||
type="tune"
|
||||
className={(this.state.showQuickSettingsMenu ? 'text-info' : '') + ' rounded hover:text-info'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showQuickSettingsMenu && (
|
||||
<QuickSettingsMenu
|
||||
onClickOutside={this.clickOutsideQuickSettingsMenu}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
application={this.application}
|
||||
/>
|
||||
)}
|
||||
<div className="relative z-footer-bar-item select-none">
|
||||
<QuickSettingsButton
|
||||
isOpen={this.state.showQuickSettingsMenu}
|
||||
toggleMenu={this.quickSettingsClickHandler}
|
||||
application={this.application}
|
||||
preferencesController={this.viewControllerManager.preferencesController}
|
||||
quickSettingsMenuController={this.viewControllerManager.quickSettingsMenuController}
|
||||
/>
|
||||
</div>
|
||||
{this.state.showBetaWarning && (
|
||||
<Fragment>
|
||||
@@ -454,7 +428,7 @@ class Footer extends PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { PreferencesController } from '@/Controllers/PreferencesController'
|
||||
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
||||
import { useRef } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Popover from '../Popover/Popover'
|
||||
import QuickSettingsMenu from '../QuickSettingsMenu/QuickSettingsMenu'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
toggleMenu: () => void
|
||||
application: WebApplication
|
||||
preferencesController: PreferencesController
|
||||
quickSettingsMenuController: QuickSettingsController
|
||||
}
|
||||
|
||||
const QuickSettingsButton = ({
|
||||
application,
|
||||
isOpen,
|
||||
toggleMenu,
|
||||
preferencesController,
|
||||
quickSettingsMenuController,
|
||||
}: Props) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
className="flex h-full w-8 cursor-pointer items-center justify-center"
|
||||
ref={buttonRef}
|
||||
>
|
||||
<div className="h-5">
|
||||
<Icon type="tune" className={(isOpen ? 'text-info' : '') + ' rounded hover:text-info'} />
|
||||
</div>
|
||||
</button>
|
||||
<Popover
|
||||
togglePopover={toggleMenu}
|
||||
anchorElement={buttonRef.current}
|
||||
open={isOpen}
|
||||
side="top"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<QuickSettingsMenu
|
||||
preferencesController={preferencesController}
|
||||
quickSettingsMenuController={quickSettingsMenuController}
|
||||
application={application}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuickSettingsButton
|
||||
@@ -1,190 +1,104 @@
|
||||
import { FunctionComponent, useMemo } from 'react'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
import {
|
||||
AccessibilityIcon,
|
||||
AccountCircleIcon,
|
||||
AddIcon,
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowsSortDownIcon,
|
||||
ArrowsSortUpIcon,
|
||||
AttachmentFileIcon,
|
||||
AuthenticatorIcon,
|
||||
CheckBoldIcon,
|
||||
CheckCircleIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ClearCircleFilledIcon,
|
||||
CloseIcon,
|
||||
CloudOffIcon,
|
||||
CodeIcon,
|
||||
CopyIcon,
|
||||
DashboardIcon,
|
||||
DownloadIcon,
|
||||
EditorIcon,
|
||||
EmailIcon,
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
FileDocIcon,
|
||||
FileIcon,
|
||||
FileImageIcon,
|
||||
FileMovIcon,
|
||||
FileMusicIcon,
|
||||
FileOtherIcon,
|
||||
FilePdfIcon,
|
||||
FilePptIcon,
|
||||
FileXlsIcon,
|
||||
FileZipIcon,
|
||||
FolderIcon,
|
||||
HashtagIcon,
|
||||
HashtagOffIcon,
|
||||
HelpIcon,
|
||||
HistoryIcon,
|
||||
InfoIcon,
|
||||
KeyboardIcon,
|
||||
LinkIcon,
|
||||
LinkOffIcon,
|
||||
ListBulleted,
|
||||
ListedIcon,
|
||||
LockFilledIcon,
|
||||
LockIcon,
|
||||
MarkdownIcon,
|
||||
MenuArrowDownAlt,
|
||||
MenuArrowDownIcon,
|
||||
MenuArrowRightIcon,
|
||||
MenuCloseIcon,
|
||||
MoreIcon,
|
||||
NotesFilledIcon,
|
||||
NotesIcon,
|
||||
PasswordIcon,
|
||||
PencilFilledIcon,
|
||||
PencilIcon,
|
||||
PencilOffIcon,
|
||||
PinFilledIcon,
|
||||
PinIcon,
|
||||
PlainTextIcon,
|
||||
PremiumFeatureIcon,
|
||||
RestoreIcon,
|
||||
RichTextIcon,
|
||||
SearchIcon,
|
||||
SecurityIcon,
|
||||
ServerIcon,
|
||||
SettingsIcon,
|
||||
SignInIcon,
|
||||
SignOutIcon,
|
||||
SortDescendingIcon,
|
||||
SpreadsheetsIcon,
|
||||
StarIcon,
|
||||
SubtractIcon,
|
||||
SyncIcon,
|
||||
TasksIcon,
|
||||
ThemesIcon,
|
||||
TrashFilledIcon,
|
||||
TrashIcon,
|
||||
TrashSweepIcon,
|
||||
TuneIcon,
|
||||
UnarchiveIcon,
|
||||
UnpinIcon,
|
||||
UserAddIcon,
|
||||
UserIcon,
|
||||
UserSwitch,
|
||||
WarningIcon,
|
||||
WindowIcon,
|
||||
} from '@standardnotes/icons'
|
||||
import * as icons from '@standardnotes/icons'
|
||||
|
||||
export const ICONS = {
|
||||
'account-circle': AccountCircleIcon,
|
||||
'arrow-left': ArrowLeftIcon,
|
||||
'arrows-sort-down': ArrowsSortDownIcon,
|
||||
'arrows-sort-up': ArrowsSortUpIcon,
|
||||
'attachment-file': AttachmentFileIcon,
|
||||
'check-bold': CheckBoldIcon,
|
||||
'check-circle': CheckCircleIcon,
|
||||
'chevron-down': ChevronDownIcon,
|
||||
'chevron-right': ChevronRightIcon,
|
||||
'clear-circle-filled': ClearCircleFilledIcon,
|
||||
'cloud-off': CloudOffIcon,
|
||||
'eye-off': EyeOffIcon,
|
||||
'file-doc': FileDocIcon,
|
||||
'file-image': FileImageIcon,
|
||||
'file-mov': FileMovIcon,
|
||||
'file-music': FileMusicIcon,
|
||||
'file-other': FileOtherIcon,
|
||||
'file-pdf': FilePdfIcon,
|
||||
'file-ppt': FilePptIcon,
|
||||
'file-xls': FileXlsIcon,
|
||||
'file-zip': FileZipIcon,
|
||||
'hashtag-off': HashtagOffIcon,
|
||||
'link-off': LinkOffIcon,
|
||||
'list-bulleted': ListBulleted,
|
||||
'lock-filled': LockFilledIcon,
|
||||
'menu-arrow-down-alt': MenuArrowDownAlt,
|
||||
'menu-arrow-down': MenuArrowDownIcon,
|
||||
'menu-arrow-right': MenuArrowRightIcon,
|
||||
'menu-close': MenuCloseIcon,
|
||||
'notes-filled': NotesFilledIcon,
|
||||
'pencil-filled': PencilFilledIcon,
|
||||
'pencil-off': PencilOffIcon,
|
||||
'pin-filled': PinFilledIcon,
|
||||
'plain-text': PlainTextIcon,
|
||||
'premium-feature': PremiumFeatureIcon,
|
||||
'rich-text': RichTextIcon,
|
||||
'sort-descending': SortDescendingIcon,
|
||||
'trash-filled': TrashFilledIcon,
|
||||
'trash-sweep': TrashSweepIcon,
|
||||
'user-add': UserAddIcon,
|
||||
'user-switch': UserSwitch,
|
||||
accessibility: AccessibilityIcon,
|
||||
add: AddIcon,
|
||||
archive: ArchiveIcon,
|
||||
authenticator: AuthenticatorIcon,
|
||||
check: CheckIcon,
|
||||
close: CloseIcon,
|
||||
code: CodeIcon,
|
||||
copy: CopyIcon,
|
||||
dashboard: DashboardIcon,
|
||||
download: DownloadIcon,
|
||||
editor: EditorIcon,
|
||||
email: EmailIcon,
|
||||
eye: EyeIcon,
|
||||
file: FileIcon,
|
||||
folder: FolderIcon,
|
||||
hashtag: HashtagIcon,
|
||||
help: HelpIcon,
|
||||
history: HistoryIcon,
|
||||
info: InfoIcon,
|
||||
keyboard: KeyboardIcon,
|
||||
link: LinkIcon,
|
||||
listed: ListedIcon,
|
||||
lock: LockIcon,
|
||||
markdown: MarkdownIcon,
|
||||
more: MoreIcon,
|
||||
notes: NotesIcon,
|
||||
password: PasswordIcon,
|
||||
pencil: PencilIcon,
|
||||
pin: PinIcon,
|
||||
restore: RestoreIcon,
|
||||
search: SearchIcon,
|
||||
security: SecurityIcon,
|
||||
server: ServerIcon,
|
||||
settings: SettingsIcon,
|
||||
signIn: SignInIcon,
|
||||
signOut: SignOutIcon,
|
||||
spreadsheets: SpreadsheetsIcon,
|
||||
star: StarIcon,
|
||||
subtract: SubtractIcon,
|
||||
sync: SyncIcon,
|
||||
tasks: TasksIcon,
|
||||
themes: ThemesIcon,
|
||||
trash: TrashIcon,
|
||||
tune: TuneIcon,
|
||||
unarchive: UnarchiveIcon,
|
||||
unpin: UnpinIcon,
|
||||
user: UserIcon,
|
||||
warning: WarningIcon,
|
||||
window: WindowIcon,
|
||||
'account-circle': icons.AccountCircleIcon,
|
||||
'arrow-left': icons.ArrowLeftIcon,
|
||||
'arrows-sort-down': icons.ArrowsSortDownIcon,
|
||||
'arrows-sort-up': icons.ArrowsSortUpIcon,
|
||||
'attachment-file': icons.AttachmentFileIcon,
|
||||
'check-bold': icons.CheckBoldIcon,
|
||||
'check-circle': icons.CheckCircleIcon,
|
||||
'chevron-down': icons.ChevronDownIcon,
|
||||
'chevron-right': icons.ChevronRightIcon,
|
||||
'clear-circle-filled': icons.ClearCircleFilledIcon,
|
||||
'cloud-off': icons.CloudOffIcon,
|
||||
'diamond-filled': icons.DiamondFilledIcon,
|
||||
'eye-off': icons.EyeOffIcon,
|
||||
'file-doc': icons.FileDocIcon,
|
||||
'file-image': icons.FileImageIcon,
|
||||
'file-mov': icons.FileMovIcon,
|
||||
'file-music': icons.FileMusicIcon,
|
||||
'file-other': icons.FileOtherIcon,
|
||||
'file-pdf': icons.FilePdfIcon,
|
||||
'file-ppt': icons.FilePptIcon,
|
||||
'file-xls': icons.FileXlsIcon,
|
||||
'file-zip': icons.FileZipIcon,
|
||||
'hashtag-off': icons.HashtagOffIcon,
|
||||
'link-off': icons.LinkOffIcon,
|
||||
'list-bulleted': icons.ListBulleted,
|
||||
'lock-filled': icons.LockFilledIcon,
|
||||
'menu-arrow-down-alt': icons.MenuArrowDownAlt,
|
||||
'menu-arrow-down': icons.MenuArrowDownIcon,
|
||||
'menu-arrow-right': icons.MenuArrowRightIcon,
|
||||
'menu-close': icons.MenuCloseIcon,
|
||||
'notes-filled': icons.NotesFilledIcon,
|
||||
'pencil-filled': icons.PencilFilledIcon,
|
||||
'pencil-off': icons.PencilOffIcon,
|
||||
'pin-filled': icons.PinFilledIcon,
|
||||
'plain-text': icons.PlainTextIcon,
|
||||
'premium-feature': icons.PremiumFeatureIcon,
|
||||
'rich-text': icons.RichTextIcon,
|
||||
'sort-descending': icons.SortDescendingIcon,
|
||||
'star-circle-filled': icons.StarCircleFilled,
|
||||
'star-filled': icons.StarFilledIcon,
|
||||
'star-variant-filled': icons.StarVariantFilledIcon,
|
||||
'trash-filled': icons.TrashFilledIcon,
|
||||
'trash-sweep': icons.TrashSweepIcon,
|
||||
'user-add': icons.UserAddIcon,
|
||||
'user-switch': icons.UserSwitch,
|
||||
accessibility: icons.AccessibilityIcon,
|
||||
add: icons.AddIcon,
|
||||
archive: icons.ArchiveIcon,
|
||||
asterisk: icons.AsteriskIcon,
|
||||
authenticator: icons.AuthenticatorIcon,
|
||||
check: icons.CheckIcon,
|
||||
close: icons.CloseIcon,
|
||||
code: icons.CodeIcon,
|
||||
copy: icons.CopyIcon,
|
||||
dashboard: icons.DashboardIcon,
|
||||
diamond: icons.DiamondIcon,
|
||||
download: icons.DownloadIcon,
|
||||
editor: icons.EditorIcon,
|
||||
email: icons.EmailIcon,
|
||||
eye: icons.EyeIcon,
|
||||
file: icons.FileIcon,
|
||||
folder: icons.FolderIcon,
|
||||
hashtag: icons.HashtagIcon,
|
||||
help: icons.HelpIcon,
|
||||
history: icons.HistoryIcon,
|
||||
info: icons.InfoIcon,
|
||||
keyboard: icons.KeyboardIcon,
|
||||
link: icons.LinkIcon,
|
||||
listed: icons.ListedIcon,
|
||||
lock: icons.LockIcon,
|
||||
markdown: icons.MarkdownIcon,
|
||||
more: icons.MoreIcon,
|
||||
notes: icons.NotesIcon,
|
||||
password: icons.PasswordIcon,
|
||||
pencil: icons.PencilIcon,
|
||||
pin: icons.PinIcon,
|
||||
restore: icons.RestoreIcon,
|
||||
search: icons.SearchIcon,
|
||||
security: icons.SecurityIcon,
|
||||
server: icons.ServerIcon,
|
||||
settings: icons.SettingsIcon,
|
||||
signIn: icons.SignInIcon,
|
||||
signOut: icons.SignOutIcon,
|
||||
spreadsheets: icons.SpreadsheetsIcon,
|
||||
star: icons.StarIcon,
|
||||
subtract: icons.SubtractIcon,
|
||||
sync: icons.SyncIcon,
|
||||
tasks: icons.TasksIcon,
|
||||
themes: icons.ThemesIcon,
|
||||
trash: icons.TrashIcon,
|
||||
tune: icons.TuneIcon,
|
||||
unarchive: icons.UnarchiveIcon,
|
||||
unpin: icons.UnpinIcon,
|
||||
user: icons.UserIcon,
|
||||
warning: icons.WarningIcon,
|
||||
window: icons.WindowIcon,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
export const PremiumFeatureIconName: IconType = 'asterisk'
|
||||
export const PremiumFeatureIconClass = 'text-info'
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import NotesOptions from '@/Components/NotesOptions/NotesOptions'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -24,43 +23,33 @@ const NotesContextMenu = ({
|
||||
noteTagsController,
|
||||
historyModalController,
|
||||
}: Props) => {
|
||||
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = notesController
|
||||
const { contextMenuOpen, contextMenuClickLocation } = notesController
|
||||
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => notesController.setContextMenuOpen(open))
|
||||
|
||||
useCloseOnClickOutside(contextMenuRef, () => notesController.setContextMenuOpen(false))
|
||||
|
||||
const reloadContextMenuLayout = useCallback(() => {
|
||||
notesController.reloadContextMenuLayout()
|
||||
}, [notesController])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', reloadContextMenuLayout)
|
||||
return () => {
|
||||
window.removeEventListener('resize', reloadContextMenuLayout)
|
||||
}
|
||||
}, [reloadContextMenuLayout])
|
||||
|
||||
return contextMenuOpen ? (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className="max-h-120 fixed z-dropdown-menu flex min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main"
|
||||
style={{
|
||||
...contextMenuPosition,
|
||||
maxHeight: contextMenuMaxHeight,
|
||||
return (
|
||||
<Popover
|
||||
align="start"
|
||||
anchorPoint={{
|
||||
x: contextMenuClickLocation.x,
|
||||
y: contextMenuClickLocation.y,
|
||||
}}
|
||||
className="py-2"
|
||||
open={contextMenuOpen}
|
||||
side="right"
|
||||
togglePopover={() => notesController.setContextMenuOpen(!contextMenuOpen)}
|
||||
>
|
||||
<NotesOptions
|
||||
application={application}
|
||||
closeOnBlur={closeOnBlur}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
noteTagsController={noteTagsController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
<div ref={contextMenuRef}>
|
||||
<NotesOptions
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
noteTagsController={noteTagsController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(NotesContextMenu)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
navigationController: NavigationController
|
||||
@@ -16,101 +15,59 @@ type Props = {
|
||||
|
||||
const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, noteTagsController }) => {
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
maxHeight: 'auto',
|
||||
})
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen)
|
||||
|
||||
const toggleTagsMenu = useCallback(() => {
|
||||
if (!isMenuOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(menuButtonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsMenuOpen(!isMenuOpen)
|
||||
}, [isMenuOpen])
|
||||
|
||||
const recalculateMenuStyle = useCallback(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(menuButtonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
const toggleMenu = useCallback(() => {
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}, [isMenuOpen, recalculateMenuStyle])
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<Disclosure open={isMenuOpen} onChange={toggleTagsMenu}>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsMenuOpen(false)
|
||||
}
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={menuButtonRef}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="hashtag" className="mr-2 text-neutral" />
|
||||
Add tag
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setIsMenuOpen(false)
|
||||
menuButtonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} max-h-120 fixed min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main`}
|
||||
>
|
||||
{navigationController.tags.map((tag) => (
|
||||
<button
|
||||
key={tag.uuid}
|
||||
className="max-w-80 flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onBlur={closeOnBlur}
|
||||
onClick={() => {
|
||||
notesController.isTagInSelectedNotes(tag)
|
||||
? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
|
||||
: notesController.addTagToSelectedNotes(tag).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`overflow-hidden overflow-ellipsis whitespace-nowrap
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.Escape) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="hashtag" className="mr-2 text-neutral" />
|
||||
Add tag
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</button>
|
||||
<Popover
|
||||
togglePopover={toggleMenu}
|
||||
anchorElement={buttonRef.current}
|
||||
open={isOpen}
|
||||
side="right"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
{navigationController.tags.map((tag) => (
|
||||
<button
|
||||
key={tag.uuid}
|
||||
className="max-w-80 flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
notesController.isTagInSelectedNotes(tag)
|
||||
? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
|
||||
: notesController.addTagToSelectedNotes(tag).catch(console.error)
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`overflow-hidden overflow-ellipsis whitespace-nowrap
|
||||
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}
|
||||
>
|
||||
{noteTagsController.getLongTitle(tag)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
>
|
||||
{noteTagsController.getLongTitle(tag)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ChangeEditorMenu from '@/Components/ChangeEditor/ChangeEditorMenu'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type ChangeEditorOptionProps = {
|
||||
application: WebApplication
|
||||
@@ -15,91 +13,48 @@ type ChangeEditorOptionProps = {
|
||||
|
||||
const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ application, note }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
maxHeight: 'auto',
|
||||
})
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const [closeOnBlur] = useCloseOnBlur(menuContainerRef, (open: boolean) => {
|
||||
setIsOpen(open)
|
||||
setIsVisible(open)
|
||||
})
|
||||
|
||||
const toggleChangeEditorMenu = useCallback(() => {
|
||||
if (!isOpen) {
|
||||
const menuStyle = calculateSubmenuStyle(buttonRef.current)
|
||||
if (menuStyle) {
|
||||
setMenuStyle(menuStyle)
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen)
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTimeout(() => {
|
||||
const newMenuStyle = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuStyle) {
|
||||
setMenuStyle(newMenuStyle)
|
||||
setIsVisible(true)
|
||||
}
|
||||
}, 5)
|
||||
}
|
||||
}, [isOpen])
|
||||
const toggleMenu = useCallback(async () => {
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<Disclosure open={isOpen} onChange={toggleChangeEditorMenu}>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.Escape) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.Escape) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="dashboard" className="mr-2 text-neutral" />
|
||||
Change note type
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</button>
|
||||
<Popover
|
||||
align="start"
|
||||
anchorElement={buttonRef.current}
|
||||
className="pt-2 md:pt-0"
|
||||
open={isOpen}
|
||||
side="right"
|
||||
togglePopover={toggleMenu}
|
||||
>
|
||||
<ChangeEditorMenu
|
||||
application={application}
|
||||
note={note}
|
||||
isVisible={isOpen}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="dashboard" className="mr-2 text-neutral" />
|
||||
Change note type
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.Escape) {
|
||||
setIsOpen(false)
|
||||
buttonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className="max-h-120 fixed flex min-w-68 flex-col overflow-y-auto rounded bg-default shadow-main"
|
||||
>
|
||||
{isOpen && (
|
||||
<ChangeEditorMenu
|
||||
application={application}
|
||||
closeOnBlur={closeOnBlur}
|
||||
note={note}
|
||||
isVisible={isVisible}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ import Spinner from '@/Components/Spinner/Spinner'
|
||||
type ListedActionsMenuProps = {
|
||||
application: WebApplication
|
||||
note: SNNote
|
||||
recalculateMenuStyle: () => void
|
||||
}
|
||||
|
||||
const ListedActionsMenu = ({ application, note, recalculateMenuStyle }: ListedActionsMenuProps) => {
|
||||
const ListedActionsMenu = ({ application, note }: ListedActionsMenuProps) => {
|
||||
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
|
||||
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
|
||||
|
||||
@@ -88,14 +87,11 @@ const ListedActionsMenu = ({ application, note, recalculateMenuStyle }: ListedAc
|
||||
console.error(err)
|
||||
} finally {
|
||||
setIsFetchingAccounts(false)
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
void fetchListedAccounts()
|
||||
}, [application, note.uuid, recalculateMenuStyle])
|
||||
}, [application, note.uuid])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import ListedActionsMenu from './ListedActionsMenu'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -14,74 +13,42 @@ type Props = {
|
||||
|
||||
const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) => {
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>({
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
maxHeight: 'auto',
|
||||
})
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen)
|
||||
|
||||
const toggleListedMenu = useCallback(() => {
|
||||
if (!isMenuOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(menuButtonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsMenuOpen(!isMenuOpen)
|
||||
}, [isMenuOpen])
|
||||
|
||||
const recalculateMenuStyle = useCallback(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(menuButtonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
const toggleMenu = useCallback(() => {
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
setTimeout(() => {
|
||||
recalculateMenuStyle()
|
||||
})
|
||||
}
|
||||
}, [isMenuOpen, recalculateMenuStyle])
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<Disclosure open={isMenuOpen} onChange={toggleListedMenu}>
|
||||
<DisclosureButton
|
||||
ref={menuButtonRef}
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="listed" className="mr-2 text-neutral" />
|
||||
Listed actions
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
ref={menuRef}
|
||||
style={{
|
||||
...menuStyle,
|
||||
position: 'fixed',
|
||||
}}
|
||||
className={`${
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
} max-h-120 fixed min-w-68 flex-col overflow-y-auto rounded bg-default pb-1 shadow-main`}
|
||||
>
|
||||
{isMenuOpen && (
|
||||
<ListedActionsMenu application={application} note={note} recalculateMenuStyle={recalculateMenuStyle} />
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.Escape) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}}
|
||||
ref={buttonRef}
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="listed" className="mr-2 text-neutral" />
|
||||
Listed actions
|
||||
</div>
|
||||
<Icon type="chevron-right" className="text-neutral" />
|
||||
</button>
|
||||
<Popover
|
||||
togglePopover={toggleMenu}
|
||||
anchorElement={buttonRef.current}
|
||||
open={isOpen}
|
||||
side="right"
|
||||
align="end"
|
||||
className="pt-2 md:pt-0"
|
||||
>
|
||||
<ListedActionsMenu application={application} note={note} />
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,11 @@ import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
||||
|
||||
type DeletePermanentlyButtonProps = {
|
||||
closeOnBlur: NotesOptionsProps['closeOnBlur']
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const DeletePermanentlyButton = ({ closeOnBlur, onClick }: DeletePermanentlyButtonProps) => (
|
||||
const DeletePermanentlyButton = ({ onClick }: DeletePermanentlyButtonProps) => (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onClick}
|
||||
>
|
||||
@@ -177,7 +175,6 @@ const NotesOptions = ({
|
||||
notesController,
|
||||
noteTagsController,
|
||||
historyModalController,
|
||||
closeOnBlur,
|
||||
}: NotesOptionsProps) => {
|
||||
const [altKeyDown, setAltKeyDown] = useState(false)
|
||||
|
||||
@@ -270,7 +267,6 @@ const NotesOptions = ({
|
||||
{notes.length === 1 && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={openRevisionHistoryModal}
|
||||
>
|
||||
@@ -285,7 +281,6 @@ const NotesOptions = ({
|
||||
onClick={() => {
|
||||
notesController.setLockSelectedNotes(!locked)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="pencil-off" className={iconClass} />
|
||||
@@ -298,7 +293,6 @@ const NotesOptions = ({
|
||||
onClick={() => {
|
||||
notesController.setHideSelectedNotePreviews(!hidePreviews)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="rich-text" className={iconClass} />
|
||||
@@ -311,7 +305,6 @@ const NotesOptions = ({
|
||||
onClick={() => {
|
||||
notesController.setProtectSelectedNotes(!protect).catch(console.error)
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<Icon type="password" className={iconClass} />
|
||||
@@ -335,7 +328,6 @@ const NotesOptions = ({
|
||||
)}
|
||||
{unpinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
notesController.setPinSelectedNotes(true)
|
||||
@@ -347,7 +339,6 @@ const NotesOptions = ({
|
||||
)}
|
||||
{pinned && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
notesController.setPinSelectedNotes(false)
|
||||
@@ -358,7 +349,6 @@ const NotesOptions = ({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={downloadSelectedItems}
|
||||
>
|
||||
@@ -366,7 +356,6 @@ const NotesOptions = ({
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={duplicateSelectedItems}
|
||||
>
|
||||
@@ -375,7 +364,6 @@ const NotesOptions = ({
|
||||
</button>
|
||||
{unarchived && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
notesController.setArchiveSelectedNotes(true).catch(console.error)
|
||||
@@ -387,7 +375,6 @@ const NotesOptions = ({
|
||||
)}
|
||||
{archived && (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={() => {
|
||||
notesController.setArchiveSelectedNotes(false).catch(console.error)
|
||||
@@ -400,14 +387,12 @@ const NotesOptions = ({
|
||||
{notTrashed &&
|
||||
(altKeyDown ? (
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await notesController.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={async () => {
|
||||
await notesController.setTrashSelectedNotes(true)
|
||||
@@ -420,7 +405,6 @@ const NotesOptions = ({
|
||||
{trashed && (
|
||||
<>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={async () => {
|
||||
await notesController.setTrashSelectedNotes(false)
|
||||
@@ -430,13 +414,11 @@ const NotesOptions = ({
|
||||
<span className="text-success">Restore</span>
|
||||
</button>
|
||||
<DeletePermanentlyButton
|
||||
closeOnBlur={closeOnBlur}
|
||||
onClick={async () => {
|
||||
await notesController.deleteNotesPermanently()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onBlur={closeOnBlur}
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={async () => {
|
||||
await notesController.emptyTrash()
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import NotesOptions from './NotesOptions'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -29,83 +26,38 @@ const NotesOptionsPanel = ({
|
||||
historyModalController,
|
||||
onClickPreprocessing,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [position, setPosition] = useState({
|
||||
top: 0,
|
||||
right: 0,
|
||||
})
|
||||
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const panelRef = useRef<HTMLDivElement>(null)
|
||||
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
|
||||
|
||||
const toggleMenu = useCallback(async () => {
|
||||
const willMenuOpen = !isOpen
|
||||
if (willMenuOpen && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
setIsOpen(willMenuOpen)
|
||||
}, [onClickPreprocessing, isOpen])
|
||||
|
||||
return (
|
||||
<Disclosure
|
||||
open={open}
|
||||
onChange={async () => {
|
||||
const rect = buttonRef.current?.getBoundingClientRect()
|
||||
if (rect) {
|
||||
const { clientHeight } = document.documentElement
|
||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||
const footerHeightInPx = footerElementRect?.height
|
||||
if (footerHeightInPx) {
|
||||
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
|
||||
}
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
})
|
||||
const newOpenState = !open
|
||||
if (newOpenState && onClickPreprocessing) {
|
||||
await onClickPreprocessing()
|
||||
}
|
||||
setOpen(newOpenState)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DisclosureButton
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
onBlur={closeOnBlur}
|
||||
ref={buttonRef}
|
||||
<>
|
||||
<button
|
||||
className="bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast"
|
||||
title="Note options menu"
|
||||
aria-label="Note options menu"
|
||||
onClick={toggleMenu}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<VisuallyHidden>Actions</VisuallyHidden>
|
||||
<Icon type="more" className="block" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setOpen(false)
|
||||
buttonRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...position,
|
||||
maxHeight,
|
||||
}}
|
||||
className={`${
|
||||
open ? 'flex' : 'hidden'
|
||||
} max-h-120 slide-down-animation fixed min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main transition-transform duration-150`}
|
||||
onBlur={closeOnBlur}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
{open && (
|
||||
<NotesOptions
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
noteTagsController={noteTagsController}
|
||||
historyModalController={historyModalController}
|
||||
closeOnBlur={closeOnBlur}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<Icon type="more" />
|
||||
</button>
|
||||
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="py-2">
|
||||
<NotesOptions
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
noteTagsController={noteTagsController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,4 @@ export type NotesOptionsProps = {
|
||||
notesController: NotesController
|
||||
noteTagsController: NoteTagsController
|
||||
historyModalController: HistoryModalController
|
||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { CSSProperties } from 'react'
|
||||
import { PopoverAlignment, PopoverSide } from './Types'
|
||||
import { OppositeSide, checkCollisions, getNonCollidingSide, getNonCollidingAlignment } from './Utils/Collisions'
|
||||
import { getPositionedPopoverRect } from './Utils/Rect'
|
||||
|
||||
const getStylesFromRect = (rect: DOMRect): CSSProperties => {
|
||||
return {
|
||||
willChange: 'transform',
|
||||
transform: `translate(${rect.x}px, ${rect.y}px)`,
|
||||
}
|
||||
}
|
||||
|
||||
type Options = {
|
||||
align: PopoverAlignment
|
||||
anchorRect?: DOMRect
|
||||
documentRect: DOMRect
|
||||
popoverRect?: DOMRect
|
||||
side: PopoverSide
|
||||
}
|
||||
|
||||
export const getPositionedPopoverStyles = ({
|
||||
align,
|
||||
anchorRect,
|
||||
documentRect,
|
||||
popoverRect,
|
||||
side,
|
||||
}: Options): [CSSProperties | null, PopoverSide, PopoverAlignment] => {
|
||||
if (!popoverRect || !anchorRect) {
|
||||
return [null, side, align]
|
||||
}
|
||||
|
||||
const matchesMediumBreakpoint = matchMedia('(min-width: 768px)').matches
|
||||
|
||||
if (!matchesMediumBreakpoint) {
|
||||
return [null, side, align]
|
||||
}
|
||||
|
||||
const rectForPreferredSide = getPositionedPopoverRect(popoverRect, anchorRect, side, align)
|
||||
const preferredSideRectCollisions = checkCollisions(rectForPreferredSide, documentRect)
|
||||
|
||||
const oppositeSide = OppositeSide[side]
|
||||
const rectForOppositeSide = getPositionedPopoverRect(popoverRect, anchorRect, oppositeSide, align)
|
||||
const oppositeSideRectCollisions = checkCollisions(rectForOppositeSide, documentRect)
|
||||
|
||||
const finalSide = getNonCollidingSide(side, preferredSideRectCollisions, oppositeSideRectCollisions)
|
||||
const finalAlignment = getNonCollidingAlignment(finalSide, align, preferredSideRectCollisions, {
|
||||
popoverRect,
|
||||
buttonRect: anchorRect,
|
||||
documentRect,
|
||||
})
|
||||
const finalPositionedRect = getPositionedPopoverRect(popoverRect, anchorRect, finalSide, finalAlignment)
|
||||
|
||||
return [getStylesFromRect(finalPositionedRect), finalSide, finalAlignment]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import PositionedPopoverContent from './PositionedPopoverContent'
|
||||
import { PopoverProps } from './Types'
|
||||
|
||||
type Props = PopoverProps & {
|
||||
open: boolean
|
||||
}
|
||||
|
||||
const Popover = ({
|
||||
align,
|
||||
anchorElement,
|
||||
anchorPoint,
|
||||
children,
|
||||
className,
|
||||
open,
|
||||
overrideZIndex,
|
||||
side,
|
||||
togglePopover,
|
||||
}: Props) => {
|
||||
return open ? (
|
||||
<>
|
||||
<PositionedPopoverContent
|
||||
align={align}
|
||||
anchorElement={anchorElement}
|
||||
anchorPoint={anchorPoint}
|
||||
className={className}
|
||||
overrideZIndex={overrideZIndex}
|
||||
side={side}
|
||||
togglePopover={togglePopover}
|
||||
>
|
||||
{children}
|
||||
</PositionedPopoverContent>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default Popover
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useDocumentRect } from '@/Hooks/useDocumentRect'
|
||||
import { useAutoElementRect } from '@/Hooks/useElementRect'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { useState } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Portal from '../Portal/Portal'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
|
||||
import { PopoverContentProps } from './Types'
|
||||
import { getPopoverMaxHeight, getAppRect } from './Utils/Rect'
|
||||
import { usePopoverCloseOnClickOutside } from './Utils/usePopoverCloseOnClickOutside'
|
||||
|
||||
const PositionedPopoverContent = ({
|
||||
align = 'end',
|
||||
anchorElement,
|
||||
anchorPoint,
|
||||
children,
|
||||
className,
|
||||
overrideZIndex,
|
||||
side = 'bottom',
|
||||
togglePopover,
|
||||
}: PopoverContentProps) => {
|
||||
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
|
||||
const popoverRect = useAutoElementRect(popoverElement)
|
||||
const anchorElementRect = useAutoElementRect(anchorElement, {
|
||||
updateOnWindowResize: true,
|
||||
})
|
||||
const anchorPointRect = DOMRect.fromRect({
|
||||
x: anchorPoint?.x,
|
||||
y: anchorPoint?.y,
|
||||
})
|
||||
const anchorRect = anchorPoint ? anchorPointRect : anchorElementRect
|
||||
const documentRect = useDocumentRect()
|
||||
|
||||
const [styles, positionedSide, positionedAlignment] = getPositionedPopoverStyles({
|
||||
align,
|
||||
anchorRect,
|
||||
documentRect,
|
||||
popoverRect: popoverRect ?? popoverElement?.getBoundingClientRect(),
|
||||
side,
|
||||
})
|
||||
|
||||
usePopoverCloseOnClickOutside({
|
||||
popoverElement,
|
||||
anchorElement,
|
||||
togglePopover,
|
||||
})
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-0 left-0 flex h-full w-full min-w-80 cursor-auto flex-col overflow-y-auto rounded bg-default shadow-main md:h-auto md:max-w-xs',
|
||||
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
maxHeight: getPopoverMaxHeight(getAppRect(documentRect), anchorRect, positionedSide, positionedAlignment),
|
||||
}}
|
||||
ref={(node) => {
|
||||
setPopoverElement(node)
|
||||
}}
|
||||
data-popover
|
||||
>
|
||||
<div className="md:hidden">
|
||||
<div className="flex items-center justify-end px-3">
|
||||
<button className="rounded-full border border-border p-1" onClick={togglePopover}>
|
||||
<Icon type="close" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export default PositionedPopoverContent
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export type PopoverState = 'closed' | 'positioning' | 'open'
|
||||
|
||||
export type PopoverElement = HTMLDivElement | HTMLMenuElement
|
||||
|
||||
export type PopoverSide = 'top' | 'left' | 'bottom' | 'right'
|
||||
|
||||
export type PopoverAlignment = 'start' | 'center' | 'end'
|
||||
|
||||
export type PopoverOptions = {
|
||||
side: PopoverSide
|
||||
align: PopoverAlignment
|
||||
}
|
||||
|
||||
export type RectCollisions = Record<PopoverSide, boolean>
|
||||
|
||||
type Point = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
type PopoverAnchorElementProps = {
|
||||
anchorElement: HTMLElement | null
|
||||
anchorPoint?: never
|
||||
}
|
||||
|
||||
type PopoverAnchorPointProps = {
|
||||
anchorPoint: Point
|
||||
anchorElement?: never
|
||||
}
|
||||
|
||||
type CommonPopoverProps = {
|
||||
align?: PopoverAlignment
|
||||
children: ReactNode
|
||||
side?: PopoverSide
|
||||
overrideZIndex?: string
|
||||
togglePopover: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type PopoverContentProps = CommonPopoverProps & {
|
||||
anchorElement?: HTMLElement | null
|
||||
anchorPoint?: Point
|
||||
}
|
||||
|
||||
export type PopoverProps =
|
||||
| (CommonPopoverProps & PopoverAnchorElementProps)
|
||||
| (CommonPopoverProps & PopoverAnchorPointProps)
|
||||
@@ -0,0 +1,86 @@
|
||||
import { PopoverSide, PopoverAlignment, RectCollisions } from '../Types'
|
||||
import { getAppRect, getPositionedPopoverRect } from './Rect'
|
||||
|
||||
export const OppositeSide: Record<PopoverSide, PopoverSide> = {
|
||||
top: 'bottom',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
right: 'left',
|
||||
}
|
||||
|
||||
export const checkCollisions = (popoverRect: DOMRect, containerRect: DOMRect): RectCollisions => {
|
||||
const appRect = getAppRect(containerRect)
|
||||
|
||||
return {
|
||||
top: popoverRect.top < appRect.top,
|
||||
left: popoverRect.left < appRect.left,
|
||||
bottom: popoverRect.bottom > appRect.bottom,
|
||||
right: popoverRect.right > appRect.right,
|
||||
}
|
||||
}
|
||||
|
||||
export const getNonCollidingSide = (
|
||||
preferredSide: PopoverSide,
|
||||
preferredSideCollisions: RectCollisions,
|
||||
oppositeSideCollisions: RectCollisions,
|
||||
): PopoverSide => {
|
||||
const oppositeSide = OppositeSide[preferredSide]
|
||||
|
||||
return preferredSideCollisions[preferredSide] && !oppositeSideCollisions[oppositeSide] ? oppositeSide : preferredSide
|
||||
}
|
||||
|
||||
const OppositeAlignment: Record<Exclude<PopoverAlignment, 'center'>, PopoverAlignment> = {
|
||||
start: 'end',
|
||||
end: 'start',
|
||||
}
|
||||
|
||||
export const getNonCollidingAlignment = (
|
||||
finalSide: PopoverSide,
|
||||
preferredAlignment: PopoverAlignment,
|
||||
collisions: RectCollisions,
|
||||
{
|
||||
popoverRect,
|
||||
buttonRect,
|
||||
documentRect,
|
||||
}: {
|
||||
popoverRect: DOMRect
|
||||
buttonRect: DOMRect
|
||||
documentRect: DOMRect
|
||||
},
|
||||
): PopoverAlignment => {
|
||||
const isHorizontalSide = finalSide === 'top' || finalSide === 'bottom'
|
||||
const boundToCheckForStart = isHorizontalSide ? 'right' : 'bottom'
|
||||
const boundToCheckForEnd = isHorizontalSide ? 'left' : 'top'
|
||||
|
||||
const prefersAligningAtStart = preferredAlignment === 'start'
|
||||
const prefersAligningAtCenter = preferredAlignment === 'center'
|
||||
const prefersAligningAtEnd = preferredAlignment === 'end'
|
||||
|
||||
if (prefersAligningAtCenter) {
|
||||
if (collisions[boundToCheckForStart]) {
|
||||
return 'end'
|
||||
}
|
||||
if (collisions[boundToCheckForEnd]) {
|
||||
return 'start'
|
||||
}
|
||||
} else {
|
||||
const oppositeAlignmentCollisions = checkCollisions(
|
||||
getPositionedPopoverRect(popoverRect, buttonRect, finalSide, OppositeAlignment[preferredAlignment]),
|
||||
documentRect,
|
||||
)
|
||||
|
||||
if (
|
||||
prefersAligningAtStart &&
|
||||
collisions[boundToCheckForStart] &&
|
||||
!oppositeAlignmentCollisions[boundToCheckForEnd]
|
||||
) {
|
||||
return 'end'
|
||||
}
|
||||
|
||||
if (prefersAligningAtEnd && collisions[boundToCheckForEnd] && !oppositeAlignmentCollisions[boundToCheckForStart]) {
|
||||
return 'start'
|
||||
}
|
||||
}
|
||||
|
||||
return preferredAlignment
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { PopoverSide, PopoverAlignment } from '../Types'
|
||||
|
||||
export const getPopoverMaxHeight = (
|
||||
appRect: DOMRect,
|
||||
buttonRect: DOMRect | undefined,
|
||||
side: PopoverSide,
|
||||
alignment: PopoverAlignment,
|
||||
): number | 'none' => {
|
||||
const matchesMediumBreakpoint = matchMedia('(min-width: 768px)').matches
|
||||
|
||||
if (!matchesMediumBreakpoint) {
|
||||
return 'none'
|
||||
}
|
||||
|
||||
const MarginFromAppBorderInPX = 10
|
||||
|
||||
let constraint = 0
|
||||
|
||||
if (buttonRect) {
|
||||
switch (side) {
|
||||
case 'top':
|
||||
constraint = appRect.height - buttonRect.top
|
||||
break
|
||||
case 'bottom':
|
||||
constraint = buttonRect.bottom
|
||||
break
|
||||
case 'left':
|
||||
case 'right':
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
constraint = buttonRect.top
|
||||
break
|
||||
case 'end':
|
||||
constraint = appRect.height - buttonRect.bottom
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return appRect.height - constraint - MarginFromAppBorderInPX
|
||||
}
|
||||
|
||||
export const getMaxHeightAdjustedRect = (rect: DOMRect, maxHeight: number) => {
|
||||
return DOMRect.fromRect({
|
||||
width: rect.width,
|
||||
height: rect.height < maxHeight ? rect.height : maxHeight,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
})
|
||||
}
|
||||
|
||||
export const getAppRect = (updatedDocumentRect?: DOMRect) => {
|
||||
const footerRect = document.querySelector('footer')?.getBoundingClientRect()
|
||||
const documentRect = updatedDocumentRect ? updatedDocumentRect : document.documentElement.getBoundingClientRect()
|
||||
|
||||
const appRect = footerRect
|
||||
? DOMRect.fromRect({
|
||||
width: documentRect.width,
|
||||
height: documentRect.height - footerRect.height,
|
||||
})
|
||||
: documentRect
|
||||
|
||||
return appRect
|
||||
}
|
||||
|
||||
export const getPositionedPopoverRect = (
|
||||
popoverRect: DOMRect,
|
||||
buttonRect: DOMRect,
|
||||
side: PopoverSide,
|
||||
align: PopoverAlignment,
|
||||
): DOMRect => {
|
||||
const { width, height } = popoverRect
|
||||
|
||||
const positionPopoverRect = DOMRect.fromRect(popoverRect)
|
||||
|
||||
switch (side) {
|
||||
case 'top': {
|
||||
positionPopoverRect.y = buttonRect.top - height
|
||||
break
|
||||
}
|
||||
case 'bottom':
|
||||
positionPopoverRect.y = buttonRect.bottom
|
||||
break
|
||||
case 'left':
|
||||
positionPopoverRect.x = buttonRect.left - width
|
||||
break
|
||||
case 'right':
|
||||
positionPopoverRect.x = buttonRect.right
|
||||
break
|
||||
}
|
||||
|
||||
if (side === 'top' || side === 'bottom') {
|
||||
switch (align) {
|
||||
case 'start':
|
||||
positionPopoverRect.x = buttonRect.left
|
||||
break
|
||||
case 'center':
|
||||
positionPopoverRect.x = buttonRect.left - width / 2 + buttonRect.width / 2
|
||||
break
|
||||
case 'end':
|
||||
positionPopoverRect.x = buttonRect.right - width
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (align) {
|
||||
case 'start':
|
||||
positionPopoverRect.y = buttonRect.top
|
||||
break
|
||||
case 'center':
|
||||
positionPopoverRect.y = buttonRect.top - height / 2 + buttonRect.height / 2
|
||||
break
|
||||
case 'end':
|
||||
positionPopoverRect.y = buttonRect.bottom - height
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return positionPopoverRect
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user