mirror of
https://github.com/standardnotes/app
synced 2026-09-15 06:45:59 -04:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f09fc74da | ||
|
|
340f4f7f09 | ||
|
|
371a47d838 | ||
|
|
b839ebb58a | ||
|
|
482c4d1ca4 | ||
|
|
5f96281948 | ||
|
|
2e79fc412c | ||
|
|
5bdbfe9fc7 | ||
|
|
dd8ccdeadc | ||
|
|
eea97362f8 | ||
|
|
01294868c9 | ||
|
|
77ec9ca335 | ||
|
|
3e6c752967 | ||
|
|
ba017d42f5 | ||
|
|
bd3df71dd4 | ||
|
|
22c6e2c56c | ||
|
|
496525ef61 | ||
|
|
6b2f8f3da2 | ||
|
|
e57b28fe29 | ||
|
|
ba4b6a580b | ||
|
|
59fc68296b | ||
|
|
69b2af7612 | ||
|
|
30dda73e90 | ||
|
|
81ea9e896c | ||
|
|
bc9247df41 | ||
|
|
71e8645ca4 | ||
|
|
72f384c215 | ||
|
|
b65e8562f2 |
@@ -1,96 +0,0 @@
|
||||
name: Mobile TestFlight
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
android:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Dev Android keystore
|
||||
run: |
|
||||
echo "${{ secrets.DEV_KEYSTORE }}" > keystore.keystore.asc
|
||||
gpg -d --passphrase "${{ secrets.DEV_KEYSTORE_PASSPHRASE }}" --batch keystore.keystore.asc > android/app/keystore.keystore
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- run: yarn build:mobile
|
||||
- name: fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
ANDROID_KEYSTORE_ALIAS: ${{ secrets.DEV_ANDROID_KEYSTORE_ALIAS }}
|
||||
ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PASSWORD }}
|
||||
GOOGLE_PLAY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY_DATA }}
|
||||
with:
|
||||
lane: 'android dev'
|
||||
subdirectory: 'packages/mobile'
|
||||
ios:
|
||||
env:
|
||||
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable && yarn install:pods
|
||||
- run: yarn build:mobile
|
||||
- name: Set ssh connection to Github
|
||||
uses: webfactory/[email protected]
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Export Apple AppStore Connect API key
|
||||
run: echo "${{ secrets.APPSTORE_CONNECT_KEY }}" > ios/Authkey.p8
|
||||
- name: Setup fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
with:
|
||||
lane: 'ios setup'
|
||||
subdirectory: 'packages/mobile'
|
||||
- name: fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
APPLE_APP_ID: ${{ secrets.DEV_APP_APPLE_ID }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ISSUER_ID }}
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
MATCH_CERTIFICATES_URL: ${{ secrets.MATCH_CERTIFICATES_URL }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
with:
|
||||
lane: 'ios dev'
|
||||
subdirectory: 'packages/mobile'
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
|
||||
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
|
||||
"start:server:web": "lerna run start --scope=@standardnotes/web",
|
||||
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.21.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.21.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.21.2",
|
||||
"version": "1.21.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.104.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.104.65",
|
||||
"version": "3.104.75",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.19.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.33",
|
||||
"version": "1.19.35",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.58.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [1.57.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.57.1",
|
||||
"version": "1.58.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -38,8 +38,6 @@ export enum FeatureIdentifier {
|
||||
TaskEditor = 'org.standardnotes.simple-task-editor',
|
||||
TokenVaultEditor = 'org.standardnotes.token-vault',
|
||||
|
||||
FilesTableView = 'org.standardnotes.files-table-view',
|
||||
|
||||
DeprecatedBoldEditor = 'org.standardnotes.bold-editor',
|
||||
DeprecatedMarkdownBasicEditor = 'org.standardnotes.simple-markdown-editor',
|
||||
DeprecatedMarkdownMathEditor = 'org.standardnotes.fancy-markdown-editor',
|
||||
@@ -53,4 +51,4 @@ export enum FeatureIdentifier {
|
||||
*/
|
||||
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
|
||||
|
||||
export const ExperimentalFeatures = [FeatureIdentifier.FilesTableView]
|
||||
export const ExperimentalFeatures = []
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
|
||||
export function experimentalFeatures(): FeatureDescription[] {
|
||||
const filesTableView: FeatureDescription = {
|
||||
identifier: FeatureIdentifier.FilesTableView,
|
||||
name: 'Files Table View',
|
||||
description:
|
||||
'Replaces the current Files view with a table view, with name, size, and date sort options. Requires reload to take effect.',
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
permission_name: PermissionName.FilesTableView,
|
||||
}
|
||||
|
||||
return [filesTableView]
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -40,5 +40,4 @@ export enum PermissionName {
|
||||
TwoFactorAuth = 'server:two-factor-auth',
|
||||
SubscriptionSharing = 'server:subscription-sharing',
|
||||
SuperEditor = 'editor:super-editor',
|
||||
FilesTableView = 'app:files-table-view',
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.0",
|
||||
"version": "1.28.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.10",
|
||||
"version": "1.14.12",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.50.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.50.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
for_platform :ios do
|
||||
for_lane :dev do
|
||||
app_identifier "com.standardnotes.standardnotes.dev"
|
||||
end
|
||||
for_lane :prod do
|
||||
app_identifier "com.standardnotes.standardnotes"
|
||||
end
|
||||
end
|
||||
|
||||
for_platform :android do
|
||||
for_lane :dev do
|
||||
package_name 'com.standardnotes.dev'
|
||||
end
|
||||
for_lane :prod do
|
||||
package_name 'com.standardnotes'
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ storage_mode("git")
|
||||
|
||||
# type("development") # The default type, can be: appstore, adhoc, enterprise or development
|
||||
|
||||
app_identifier(["com.standardnotes.standardnotes", "com.standardnotes.standardnotes.dev"])
|
||||
app_identifier(["com.standardnotes.standardnotes"])
|
||||
|
||||
# For all available options run `fastlane match --help`
|
||||
# Remove the # in the beginning of the line to enable the other options
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.50.0",
|
||||
"version": "3.50.11",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -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.42.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [1.41.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
# [1.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.41.0",
|
||||
"version": "1.42.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -18,4 +18,5 @@ export interface TagPreferences {
|
||||
editorIdentifier?: EditorIdentifier
|
||||
entryMode?: 'normal' | 'daily'
|
||||
panelWidth?: number
|
||||
useTableView?: boolean
|
||||
}
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.95](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.94](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.93](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.92](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.91](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.90](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.88](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.87](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.87",
|
||||
"version": "1.4.98",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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.12.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.12.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.12.12",
|
||||
"version": "1.12.13",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.52.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.52.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.52.0",
|
||||
"version": "1.52.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -182,5 +182,6 @@ export const ErrorAlertStrings = {
|
||||
|
||||
export const KeychainRecoveryStrings = {
|
||||
Title: 'Restore Keychain',
|
||||
Text: "We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password to restore your account keys.",
|
||||
Text: (email: string) =>
|
||||
`We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password for "${email}" to restore your account keys.`,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
import { DeinitSource } from '../Application/DeinitSource'
|
||||
|
||||
@@ -8,4 +9,5 @@ export interface UserClientInterface {
|
||||
}>
|
||||
signOut(force?: boolean, source?: DeinitSource): Promise<void>
|
||||
submitUserRequest(requestType: UserRequestType): Promise<boolean>
|
||||
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
|
||||
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
||||
@@ -470,6 +471,11 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
}
|
||||
}
|
||||
|
||||
public async populateSessionFromDemoShareToken(token: Base64String): Promise<void> {
|
||||
await this.sessionManager.populateSessionFromDemoShareToken(token)
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
|
||||
}
|
||||
|
||||
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
|
||||
const identifier = UuidGenerator.GenerateUuid()
|
||||
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.158.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.158.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.158.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -53,7 +53,7 @@ Once the server infrastructure is ready, and you've built all packages, you can
|
||||
|
||||
In app repo:
|
||||
```
|
||||
yarn start:server:e2e
|
||||
yarn e2e
|
||||
```
|
||||
|
||||
Once you are finished you can close the running local server on E2E repo by typing:
|
||||
|
||||
@@ -1581,6 +1581,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.identifier,
|
||||
{
|
||||
loadBatchSize: this.options.loadBatchSize,
|
||||
sleepBetweenBatches: this.options.sleepBetweenBatches,
|
||||
},
|
||||
this.internalEventBus,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { ApplicationSyncOptions } from './OptionalOptions'
|
||||
import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions'
|
||||
|
||||
export interface ApplicationOptionsWhichHaveDefaults {
|
||||
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
|
||||
sleepBetweenBatches: ApplicationSyncOptions['sleepBetweenBatches']
|
||||
allowNoteSelectionStatePersistence: ApplicationDisplayOptions['allowNoteSelectionStatePersistence']
|
||||
allowMultipleSelection: ApplicationDisplayOptions['allowMultipleSelection']
|
||||
}
|
||||
|
||||
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
|
||||
loadBatchSize: 700,
|
||||
sleepBetweenBatches: 10,
|
||||
allowMultipleSelection: true,
|
||||
allowNoteSelectionStatePersistence: true,
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@ export interface ApplicationSyncOptions {
|
||||
* The size of the item batch to decrypt and render upon application load.
|
||||
*/
|
||||
loadBatchSize: number
|
||||
|
||||
sleepBetweenBatches: number
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ApplicationDisplayOptions {}
|
||||
export interface ApplicationDisplayOptions {
|
||||
allowNoteSelectionStatePersistence: boolean
|
||||
allowMultipleSelection: boolean
|
||||
}
|
||||
|
||||
export interface ApplicationOptionalConfiguratioOptions {
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { AnyKeyParamsContent, KeyParamsContent004 } from '@standardnotes/common'
|
||||
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
|
||||
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
|
||||
import { Migration } from '@Lib/Migrations/Migration'
|
||||
@@ -165,7 +165,7 @@ export class BaseMigration extends Migration {
|
||||
}
|
||||
|
||||
private async repairMissingKeychain() {
|
||||
const rawAccountParams = await this.reader.getAccountKeyParams()
|
||||
const rawAccountParams = (await this.reader.getAccountKeyParams()) as AnyKeyParamsContent
|
||||
|
||||
/** Choose an item to decrypt against */
|
||||
const allItems = (
|
||||
@@ -196,14 +196,14 @@ export class BaseMigration extends Migration {
|
||||
ChallengeReason.Custom,
|
||||
false,
|
||||
KeychainRecoveryStrings.Title,
|
||||
KeychainRecoveryStrings.Text,
|
||||
KeychainRecoveryStrings.Text((rawAccountParams as KeyParamsContent004).identifier),
|
||||
)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.services.challengeService.addChallengeObserver(challenge, {
|
||||
onNonvalidatedSubmit: async (challengeResponse) => {
|
||||
const password = challengeResponse.values[0].value as string
|
||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent)
|
||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams)
|
||||
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
|
||||
|
||||
/** TS can't detect we returned early above if itemToDecrypt is null */
|
||||
|
||||
@@ -298,6 +298,9 @@ export class SNSyncService
|
||||
? chunks.fullEntries.remainingChunks
|
||||
: chunks.keys.remainingChunks
|
||||
|
||||
let chunkIndex = 0
|
||||
const ChunkIndexOfContentTypePriorityItems = 0
|
||||
|
||||
for (const chunk of remainingChunks) {
|
||||
const dbEntries = isChunkFullEntry(chunk)
|
||||
? chunk.entries
|
||||
@@ -314,7 +317,14 @@ export class SNSyncService
|
||||
.filter(isNotUndefined)
|
||||
|
||||
await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount)
|
||||
|
||||
const shouldSleepOnlyAfterFirstRegularBatch = chunkIndex > ChunkIndexOfContentTypePriorityItems
|
||||
if (shouldSleepOnlyAfterFirstRegularBatch) {
|
||||
await sleep(this.options.sleepBetweenBatches, false, 'Sleeping to allow interface to update')
|
||||
}
|
||||
|
||||
totalProcessedCount += payloads.length
|
||||
chunkIndex++
|
||||
}
|
||||
|
||||
this.databaseLoaded = true
|
||||
@@ -353,8 +363,6 @@ export class SNSyncService
|
||||
if (currentPosition != undefined && payloadCount != undefined) {
|
||||
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
||||
}
|
||||
|
||||
await sleep(1, false)
|
||||
}
|
||||
|
||||
private setLastSyncToken(token: string) {
|
||||
|
||||
@@ -20,16 +20,19 @@ describe('application instances', () => {
|
||||
})
|
||||
|
||||
it('two distinct applications should not share model manager state', async () => {
|
||||
const app1 = await Factory.createAndInitializeApplication('app1')
|
||||
const app2 = await Factory.createAndInitializeApplication('app2')
|
||||
expect(app1.payloadManager).to.equal(app1.payloadManager)
|
||||
expect(app1.payloadManager).to.not.equal(app2.payloadManager)
|
||||
const context1 = await Factory.createAppContext({ identifier: 'app1' })
|
||||
const context2 = await Factory.createAppContext({ identifier: 'app2' })
|
||||
await Promise.all([context1.launch(), context2.launch()])
|
||||
|
||||
await Factory.createMappedNote(app1)
|
||||
expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
await Factory.safeDeinit(app1)
|
||||
await Factory.safeDeinit(app2)
|
||||
expect(context1.application.payloadManager).to.equal(context1.application.payloadManager)
|
||||
expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager)
|
||||
|
||||
await Factory.createMappedNote(context1.application)
|
||||
expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
await context1.deinit()
|
||||
await context2.deinit()
|
||||
})
|
||||
|
||||
it('two distinct applications should not share storage manager state', async () => {
|
||||
|
||||
@@ -16,7 +16,9 @@ describe('basic auth', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
@@ -402,7 +404,7 @@ describe('basic auth', function () {
|
||||
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
this.application = await this.context.signout()
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
|
||||
|
||||
@@ -163,6 +163,13 @@ export class AppContext {
|
||||
return newApplication
|
||||
}
|
||||
|
||||
async signout() {
|
||||
await this.application.user.signOut()
|
||||
await this.initialize()
|
||||
await this.launch()
|
||||
return this.application
|
||||
}
|
||||
|
||||
syncWithIntegrityCheck() {
|
||||
return this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
|
||||
}
|
||||
@@ -189,11 +196,36 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
awaitUserPrefsSingletonCreation() {
|
||||
const preferences = this.application.preferencesService.preferences
|
||||
if (preferences) {
|
||||
return
|
||||
}
|
||||
|
||||
let didCompleteRelevantSync = false
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((eventName, data) => {
|
||||
if (!didCompleteRelevantSync) {
|
||||
if (data?.savedPayloads) {
|
||||
const matching = data.savedPayloads.find((p) => {
|
||||
return p.content_type === ContentType.UserPrefs
|
||||
})
|
||||
if (matching) {
|
||||
didCompleteRelevantSync = true
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
|
||||
await this.application.prepareForLaunch({
|
||||
receiveChallenge: receiveChallenge || this.handleChallenge,
|
||||
})
|
||||
await this.application.launch(awaitDatabaseLoad)
|
||||
await this.awaitUserPrefsSingletonCreation()
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
|
||||
@@ -21,7 +21,9 @@ describe('app models', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
this.application = this.context.application
|
||||
await this.context.launch()
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -13,6 +13,7 @@ describe('importing', function () {
|
||||
let application
|
||||
let email
|
||||
let password
|
||||
let context
|
||||
|
||||
beforeEach(function () {
|
||||
localStorage.clear()
|
||||
@@ -20,11 +21,16 @@ describe('importing', function () {
|
||||
|
||||
const setup = async ({ fakeCrypto }) => {
|
||||
expectedItemCount = BaseItemCounts.DefaultItems
|
||||
|
||||
if (fakeCrypto) {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
context = await Factory.createAppContext()
|
||||
} else {
|
||||
application = await Factory.createInitAppWithRealCrypto()
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
}
|
||||
|
||||
await context.launch()
|
||||
application = context.application
|
||||
|
||||
email = UuidGenerator.GenerateUuid()
|
||||
password = UuidGenerator.GenerateUuid()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
@@ -9,7 +9,9 @@ const expect = chai.expect
|
||||
describe('model manager mapping', () => {
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -14,7 +14,9 @@ describe('notes and tags', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -7,7 +7,9 @@ const expect = chai.expect
|
||||
|
||||
describe('tags as folders', () => {
|
||||
beforeEach(async function () {
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
||||
describe('preferences', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
|
||||
@@ -31,7 +31,11 @@ describe('singletons', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
this.registerUser = async () => {
|
||||
|
||||
@@ -16,7 +16,9 @@ describe('storage manager', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedKeyCount = BASE_KEY_COUNT
|
||||
this.application = await Factory.createInitAppWithFakeCrypto(Environment.Mobile)
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
@@ -221,7 +223,8 @@ describe('storage manager', function () {
|
||||
expect(decrypted.content).to.be.an.instanceof(Object)
|
||||
})
|
||||
|
||||
it('disabling storage encryption should store items without encryption', async function () {
|
||||
/** @TODO: Storage encryption disable is no longer available, remove tests and associated functionality */
|
||||
it.skip('disabling storage encryption should store items without encryption', async function () {
|
||||
await Factory.registerUserToApplication({
|
||||
application: this.application,
|
||||
email: this.email,
|
||||
|
||||
@@ -13,7 +13,9 @@ describe('offline syncing', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('online syncing', function () {
|
||||
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
this.application = await this.context.signout()
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
||||
describe('upgrading', () => {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
this.passcode = '1234'
|
||||
@@ -170,7 +174,7 @@ describe('upgrading', () => {
|
||||
/** Delete default items key that is created on launch */
|
||||
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
|
||||
await this.application.itemManager.setItemToBeDeleted(itemsKey)
|
||||
expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(0)
|
||||
expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
|
||||
|
||||
Factory.createMappedNote(this.application)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.158.0",
|
||||
"version": "2.158.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.3.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
## [1.3.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where file upload toast would close before completion if you switched windows ([30dda73](https://github.com/standardnotes/app/commit/30dda73e9078bbccf91c3f43307250c84a17e8bd))
|
||||
|
||||
## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/toast",
|
||||
"version": "1.3.15",
|
||||
"version": "1.3.17",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -27,8 +27,12 @@ export const ToastTimer: FunctionComponent<Props> = ({ toast, index }) => {
|
||||
const remainingTimeRef = useRef(duration)
|
||||
|
||||
const dismissToastOnEnd = useCallback(() => {
|
||||
if (!shouldAutoClose) {
|
||||
return
|
||||
}
|
||||
|
||||
dismissToast(toast.id)
|
||||
}, [toast.id])
|
||||
}, [shouldAutoClose, toast.id])
|
||||
|
||||
const clearTimer = useCallback(() => {
|
||||
if (toastTimerIdRef.current) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.23.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where the importer would not correctly parse Google Keep JSON notes ([2e79fc4](https://github.com/standardnotes/app/commit/2e79fc412c8b57b175381c4f59593a03906d89a1))
|
||||
|
||||
## [1.23.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.23.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
|
||||
|
||||
## [1.22.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.22.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.22.2",
|
||||
"version": "1.23.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -98,13 +98,13 @@ export class GoogleKeepConverter {
|
||||
|
||||
static isValidGoogleKeepJson(json: any): boolean {
|
||||
return (
|
||||
json.title &&
|
||||
json.textContent &&
|
||||
json.userEditedTimestampUsec &&
|
||||
typeof json.title === 'string' &&
|
||||
typeof json.textContent === 'string' &&
|
||||
typeof json.userEditedTimestampUsec === 'number' &&
|
||||
typeof json.isArchived === 'boolean' &&
|
||||
typeof json.isTrashed === 'boolean' &&
|
||||
typeof json.isPinned === 'boolean' &&
|
||||
json.color
|
||||
typeof json.color === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,6 @@ export enum KeyboardKey {
|
||||
Home = 'Home',
|
||||
End = 'End',
|
||||
Space = ' ',
|
||||
PageUp = 'PageUp',
|
||||
PageDown = 'PageDown',
|
||||
}
|
||||
|
||||
@@ -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.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/utils
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/utils
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/utils",
|
||||
"version": "1.15.1",
|
||||
"version": "1.15.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -519,9 +519,9 @@ export function truncateHexString(string: string, desiredBits: number) {
|
||||
* When awaited, this function allows code execution to pause for a set time.
|
||||
* Should be used primarily for testing.
|
||||
*/
|
||||
export async function sleep(milliseconds: number, warn = true): Promise<void> {
|
||||
export async function sleep(milliseconds: number, warn = true, desc = ''): Promise<void> {
|
||||
if (warn) {
|
||||
console.warn(`Sleeping for ${milliseconds}ms`)
|
||||
console.warn(`Sleeping for ${milliseconds}ms ${desc}`)
|
||||
}
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(function () {
|
||||
|
||||
@@ -3,6 +3,59 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where search button would not show up in Files view ([340f4f7](https://github.com/standardnotes/app/commit/340f4f7f09c28f7818cb4a7f1ccaa387ef0283c9))
|
||||
|
||||
## [3.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where the "Attached to" in table view would report an extra number of links ([482c4d1](https://github.com/standardnotes/app/commit/482c4d1ca4499ae4e80f592176648ee2c03f16b8))
|
||||
|
||||
## [3.136.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [3.135.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.135.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
|
||||
|
||||
# [3.134.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added linked items container & autocomplete input to file preview modal ([22c6e2c](https://github.com/standardnotes/app/commit/22c6e2c56c656cdbbc9788e5e9dc9112b22a9127))
|
||||
|
||||
# [3.133.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* Added "Add tag" option to file context menu ([ba4b6a5](https://github.com/standardnotes/app/commit/ba4b6a580b2b18d2c383475c8723fb66fa9fecdf))
|
||||
* Added file options menu to file preview modal ([e57b28f](https://github.com/standardnotes/app/commit/e57b28fe29f230754c247bf2a769355a7fbd30cb))
|
||||
|
||||
## [3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.132.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,5 +1,135 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.136.3",
|
||||
"title": "[3.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where search button would not show up in Files view ([340f4f7](https://github.com/standardnotes/app/commit/340f4f7f09c28f7818cb4a7f1ccaa387ef0283c9))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where search button would not show up in Files view (340f4f7)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where search button would not show up in Files view (340f4f7)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.136.2",
|
||||
"title": "[3.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where the \"Attached to\" in table view would report an extra number of links ([482c4d1](https://github.com/standardnotes/app/commit/482c4d1ca4499ae4e80f592176648ee2c03f16b8))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where the \"Attached to\" in table view would report an extra number of links (482c4d1)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where the \"Attached to\" in table view would report an extra number of links (482c4d1)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.136.1",
|
||||
"title": "[3.136.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.136.0",
|
||||
"title": "[3.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Added per-tag preference to use table layout and removed \"Files Table View\" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Added per-tag preference to use table layout and removed \"Files Table View\" from Labs (dd8ccde)"
|
||||
],
|
||||
"Features": [
|
||||
"Added per-tag preference to use table layout and removed \"Files Table View\" from Labs (dd8ccde)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.135.1",
|
||||
"title": "[3.135.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.135.0",
|
||||
"title": "[3.135.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Added keyboard navigation to Files table view (#2131) (ba017d4)"
|
||||
],
|
||||
"Features": [
|
||||
"Added keyboard navigation to Files table view (#2131) (ba017d4)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.134.0",
|
||||
"title": "[3.134.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Added linked items container & autocomplete input to file preview modal ([22c6e2c](https://github.com/standardnotes/app/commit/22c6e2c56c656cdbbc9788e5e9dc9112b22a9127))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Added linked items container & autocomplete input to file preview modal (22c6e2c)"
|
||||
],
|
||||
"Features": [
|
||||
"Added linked items container & autocomplete input to file preview modal (22c6e2c)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.133.0",
|
||||
"title": "[3.133.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Added \"Add tag\" option to file context menu ([ba4b6a5](https://github.com/standardnotes/app/commit/ba4b6a580b2b18d2c383475c8723fb66fa9fecdf))\n* Added file options menu to file preview modal ([e57b28f](https://github.com/standardnotes/app/commit/e57b28fe29f230754c247bf2a769355a7fbd30cb))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Added \"Add tag\" option to file context menu (ba4b6a5)",
|
||||
"Added file options menu to file preview modal (e57b28f)"
|
||||
],
|
||||
"Features": [
|
||||
"Added \"Add tag\" option to file context menu (ba4b6a5)",
|
||||
"Added file options menu to file preview modal (e57b28f)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.132.3",
|
||||
"title": "[3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.132.2",
|
||||
"title": "[3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.132.0",
|
||||
"title": "[3.132.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.132.0",
|
||||
"version": "3.136.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -78,7 +78,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
appVersion: deviceInterface.appVersion,
|
||||
webSocketUrl: webSocketUrl,
|
||||
loadBatchSize:
|
||||
deviceInterface.environment === Environment.Mobile ? 100 : ApplicationOptionsDefaults.loadBatchSize,
|
||||
deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.loadBatchSize,
|
||||
sleepBetweenBatches:
|
||||
deviceInterface.environment === Environment.Mobile ? 250 : ApplicationOptionsDefaults.sleepBetweenBatches,
|
||||
allowMultipleSelection: deviceInterface.environment !== Environment.Mobile,
|
||||
allowNoteSelectionStatePersistence: deviceInterface.environment !== Environment.Mobile,
|
||||
})
|
||||
|
||||
makeObservable(this, {
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { formatLastSyncDate } from '@/Utils/DateUtils'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
|
||||
type Props = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -85,22 +84,9 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
setMenuPane(AccountMenuPane.SignIn)
|
||||
}, [setMenuPane])
|
||||
|
||||
const openFileSend = useCallback(() => {
|
||||
const link = 'https://filesend.standardnotes.com/'
|
||||
|
||||
if (application.isNativeMobileWeb()) {
|
||||
application.mobileDevice().openUrl(link)
|
||||
return
|
||||
}
|
||||
|
||||
window.open(link, '_blank')
|
||||
}, [application])
|
||||
|
||||
const CREATE_ACCOUNT_INDEX = 1
|
||||
const SWITCHER_INDEX = 0
|
||||
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-1 mb-1 flex items-center justify-between px-3">
|
||||
@@ -186,12 +172,6 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
</div>
|
||||
<span className="text-neutral">v{application.version}</span>
|
||||
</MenuItem>
|
||||
{isMobileScreen && (
|
||||
<MenuItem onClick={openFileSend}>
|
||||
<Icon type="open-in" className={iconClassName} />
|
||||
Open FileSend
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={() => viewControllerManager.isImportModalVisible.set(true)}>
|
||||
<Icon type="archive" className={iconClassName} />
|
||||
Import
|
||||
|
||||
@@ -89,7 +89,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
return
|
||||
}
|
||||
|
||||
void application.sessions.populateSessionFromDemoShareToken(token)
|
||||
void application.user.populateSessionFromDemoShareToken(token)
|
||||
}, [application])
|
||||
|
||||
const onAppLaunch = useCallback(() => {
|
||||
@@ -216,6 +216,8 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
<FileContextMenuWrapper
|
||||
filesController={viewControllerManager.filesController}
|
||||
selectionController={viewControllerManager.selectionController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
/>
|
||||
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||
<ConfirmSignoutContainer
|
||||
|
||||
@@ -12,7 +12,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl
|
||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||
import { ElementIds } from '@/Constants/ElementIDs'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { ContentType, FeatureIdentifier, SNTag } from '@standardnotes/snjs'
|
||||
import { ContentType, SNTag } from '@standardnotes/snjs'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -97,8 +97,6 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
|
||||
const hasNotes = items.some((item) => item.content_type === ContentType.Note)
|
||||
|
||||
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -112,13 +110,7 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
>
|
||||
{items.map((item, index) => {
|
||||
const previousIndex = index - 1
|
||||
const previousItem = previousIndex >= 0 ? items[previousIndex] : undefined
|
||||
|
||||
const nextIndex = index + 1
|
||||
const nextItem = nextIndex < items.length ? items[nextIndex] : undefined
|
||||
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<ContentListItem
|
||||
key={item.uuid}
|
||||
@@ -134,8 +126,6 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
onSelect={selectItem}
|
||||
tags={getTagsForItem(item)}
|
||||
notesController={notesController}
|
||||
isPreviousItemTiled={previousItem?.content_type === ContentType.File && !isFilesTableViewEnabled}
|
||||
isNextItemTiled={nextItem?.content_type === ContentType.File && !isFilesTableViewEnabled}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import { ContentType, FeatureIdentifier, FileItem, SNNote } from '@standardnotes/snjs'
|
||||
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs'
|
||||
import React, { FunctionComponent } from 'react'
|
||||
import FileListItem from './FileListItem'
|
||||
import FileListItemCard from './FileListItemCard'
|
||||
import NoteListItem from './NoteListItem'
|
||||
import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
|
||||
import { ListableContentItem } from './Types/ListableContentItem'
|
||||
|
||||
const ContentListItem: FunctionComponent<AbstractListItemProps<ListableContentItem>> = (props) => {
|
||||
const isFilesTableViewEnabled = props.application.features.isExperimentalFeatureEnabled(
|
||||
FeatureIdentifier.FilesTableView,
|
||||
)
|
||||
|
||||
switch (props.item.content_type) {
|
||||
case ContentType.Note:
|
||||
return <NoteListItem {...props} item={props.item as SNNote} />
|
||||
case ContentType.File: {
|
||||
if (isFilesTableViewEnabled) {
|
||||
return <FileListItem {...props} item={props.item as FileItem} />
|
||||
}
|
||||
|
||||
return <FileListItemCard {...props} item={props.item as FileItem} />
|
||||
return <FileListItem {...props} item={props.item as FileItem} />
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@standardnotes/ui-services'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||
import { FeatureIdentifier, FileItem, PrefKey, SystemViewId, WebAppEvent } from '@standardnotes/snjs'
|
||||
import { FileItem, PrefKey, WebAppEvent } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { forwardRef, useCallback, useEffect, useMemo } from 'react'
|
||||
import ContentList from '@/Components/ContentListView/ContentList'
|
||||
@@ -36,9 +36,11 @@ import { FeatureName } from '@/Controllers/FeatureName'
|
||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||
import { useForwardedRef } from '@/Hooks/useForwardedRef'
|
||||
import FloatingAddButton from './FloatingAddButton'
|
||||
import FilesTableView from '../FilesTableView/FilesTableView'
|
||||
import ContentTableView from '../ContentTableView/ContentTableView'
|
||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||
import { PaneController } from '@/Controllers/PaneController/PaneController'
|
||||
|
||||
type Props = {
|
||||
accountMenuController: AccountMenuController
|
||||
@@ -52,6 +54,8 @@ type Props = {
|
||||
searchOptionsController: SearchOptionsController
|
||||
linkingController: LinkingController
|
||||
featuresController: FeaturesController
|
||||
historyModalController: HistoryModalController
|
||||
paneController: PaneController
|
||||
className?: string
|
||||
id: string
|
||||
children?: React.ReactNode
|
||||
@@ -72,6 +76,8 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
searchOptionsController,
|
||||
linkingController,
|
||||
featuresController,
|
||||
historyModalController,
|
||||
paneController,
|
||||
className,
|
||||
id,
|
||||
children,
|
||||
@@ -91,6 +97,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
renderedItems,
|
||||
items,
|
||||
isCurrentNoteTemplate,
|
||||
isTableViewEnabled,
|
||||
} = itemListController
|
||||
|
||||
const innerRef = useForwardedRef(ref)
|
||||
@@ -182,9 +189,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
|
||||
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
|
||||
const shouldShowFilesTableView =
|
||||
isFilesTableViewEnabled && !isMobileScreen && selectedTag?.uuid === SystemViewId.Files
|
||||
const shouldUseTableView = (isFilesSmartView || isTableViewEnabled) && !isMobileScreen
|
||||
|
||||
useEffect(() => {
|
||||
const searchBarElement = document.getElementById(ElementIds.SearchBar)
|
||||
@@ -208,7 +213,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
if (searchBarElement === document.activeElement) {
|
||||
searchBarElement?.blur()
|
||||
}
|
||||
if (shouldShowFilesTableView) {
|
||||
if (shouldUseTableView) {
|
||||
return
|
||||
}
|
||||
selectNextItem()
|
||||
@@ -218,7 +223,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||
element: document.body,
|
||||
onKeyDown: () => {
|
||||
if (shouldShowFilesTableView) {
|
||||
if (shouldUseTableView) {
|
||||
return
|
||||
}
|
||||
selectPreviousItem()
|
||||
@@ -262,7 +267,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
selectNextItem,
|
||||
selectPreviousItem,
|
||||
selectionController,
|
||||
shouldShowFilesTableView,
|
||||
shouldUseTableView,
|
||||
])
|
||||
|
||||
const shortcutForCreate = useMemo(
|
||||
@@ -316,18 +321,19 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
addButtonLabel={addButtonLabel}
|
||||
addNewItem={addNewItem}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isFilesTableViewEnabled={isFilesTableViewEnabled}
|
||||
isTableViewEnabled={isTableViewEnabled || isFilesSmartView}
|
||||
optionsSubtitle={optionsSubtitle}
|
||||
selectedTag={selectedTag}
|
||||
filesController={filesController}
|
||||
itemListController={itemListController}
|
||||
paneController={paneController}
|
||||
/>
|
||||
)}
|
||||
{(!shouldShowFilesTableView || isMobileScreen) && (
|
||||
{(!shouldUseTableView || isMobileScreen) && (
|
||||
<SearchBar
|
||||
itemListController={itemListController}
|
||||
searchOptionsController={searchOptionsController}
|
||||
hideOptions={shouldShowFilesTableView}
|
||||
hideOptions={shouldUseTableView}
|
||||
/>
|
||||
)}
|
||||
<NoAccountWarning
|
||||
@@ -352,12 +358,16 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
<p className="empty-items-list opacity-50">Loading...</p>
|
||||
) : null}
|
||||
{!dailyMode && renderedItems.length ? (
|
||||
shouldShowFilesTableView ? (
|
||||
<FilesTableView
|
||||
shouldUseTableView ? (
|
||||
<ContentTableView
|
||||
items={items}
|
||||
application={application}
|
||||
filesController={filesController}
|
||||
featuresController={featuresController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
) : (
|
||||
<ContentList
|
||||
|
||||
+9
-12
@@ -13,6 +13,7 @@ import AddItemMenuButton from './AddItemMenuButton'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import SearchButton from './SearchButton'
|
||||
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||
import { PaneController } from '@/Controllers/PaneController/PaneController'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -21,11 +22,12 @@ type Props = {
|
||||
addButtonLabel: string
|
||||
addNewItem: () => void
|
||||
isFilesSmartView: boolean
|
||||
isFilesTableViewEnabled: boolean
|
||||
isTableViewEnabled: boolean
|
||||
optionsSubtitle?: string
|
||||
selectedTag: AnyTag
|
||||
filesController: FilesController
|
||||
itemListController: ItemListController
|
||||
paneController: PaneController
|
||||
}
|
||||
|
||||
const ContentListHeader = ({
|
||||
@@ -35,11 +37,12 @@ const ContentListHeader = ({
|
||||
addButtonLabel,
|
||||
addNewItem,
|
||||
isFilesSmartView,
|
||||
isFilesTableViewEnabled,
|
||||
isTableViewEnabled,
|
||||
optionsSubtitle,
|
||||
selectedTag,
|
||||
filesController,
|
||||
itemListController,
|
||||
paneController,
|
||||
}: Props) => {
|
||||
const displayOptionsContainerRef = useRef<HTMLDivElement>(null)
|
||||
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -79,19 +82,13 @@ const ContentListHeader = ({
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isOpen={showDisplayOptionsMenu}
|
||||
selectedTag={selectedTag}
|
||||
paneController={paneController}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
showDisplayOptionsMenu,
|
||||
toggleDisplayOptionsMenu,
|
||||
displayOptionsButtonRef,
|
||||
application,
|
||||
isFilesSmartView,
|
||||
selectedTag,
|
||||
])
|
||||
}, [showDisplayOptionsMenu, toggleDisplayOptionsMenu, application, isFilesSmartView, selectedTag, paneController])
|
||||
|
||||
const AddButton = useMemo(() => {
|
||||
return (
|
||||
@@ -106,12 +103,12 @@ const ContentListHeader = ({
|
||||
}, [addButtonLabel, addNewItem, filesController, isDailyEntry, isFilesSmartView])
|
||||
|
||||
const SearchBarButton = useMemo(() => {
|
||||
if (!isFilesSmartView || !isFilesTableViewEnabled || isMobileScreen) {
|
||||
if (!isTableViewEnabled || isMobileScreen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <SearchButton itemListController={itemListController} />
|
||||
}, [isFilesSmartView, isFilesTableViewEnabled, isMobileScreen, itemListController])
|
||||
}, [isTableViewEnabled, isMobileScreen, itemListController])
|
||||
|
||||
const FolderName = useMemo(() => {
|
||||
return (
|
||||
|
||||
+37
-4
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
CollectionSort,
|
||||
CollectionSortProperty,
|
||||
FeatureIdentifier,
|
||||
IconType,
|
||||
isSmartView,
|
||||
isSystemView,
|
||||
@@ -25,6 +24,8 @@ import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscripti
|
||||
import MenuRadioButtonItem from '@/Components/Menu/MenuRadioButtonItem'
|
||||
import MenuSwitchButtonItem from '@/Components/Menu/MenuSwitchButtonItem'
|
||||
import { Pill } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
||||
|
||||
const DailyEntryModeEnabled = true
|
||||
|
||||
@@ -33,6 +34,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
isOpen,
|
||||
isFilesSmartView,
|
||||
selectedTag,
|
||||
paneController,
|
||||
}) => {
|
||||
const isSystemTag = isSmartView(selectedTag) && isSystemView(selectedTag)
|
||||
const selectedTagPreferences = isSystemTag
|
||||
@@ -217,6 +219,14 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
void changePreferences({ entryMode: isDailyEntry ? 'normal' : 'daily' })
|
||||
}, [isDailyEntry, changePreferences])
|
||||
|
||||
const toggleTableView = useCallback(() => {
|
||||
const useTableView = !preferences.useTableView
|
||||
void changePreferences({ useTableView })
|
||||
if (useTableView) {
|
||||
paneController.setPaneLayout(PaneLayout.TableView)
|
||||
}
|
||||
}, [preferences.useTableView, changePreferences, paneController])
|
||||
|
||||
const TabButton: FunctionComponent<{
|
||||
label: string
|
||||
mode: PreferenceMode
|
||||
@@ -250,8 +260,9 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const isFilesTableViewEnabled = application.features.isExperimentalFeatureEnabled(FeatureIdentifier.FilesTableView)
|
||||
const shouldHideNonApplicableOptions = isFilesTableViewEnabled && selectedTag?.uuid === SystemViewId.Files
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const isTableViewEnabled = Boolean(isFilesSmartView || preferences.useTableView)
|
||||
const shouldHideNonApplicableOptions = isTableViewEnabled && !isMobileScreen
|
||||
|
||||
return (
|
||||
<Menu className="text-sm" a11yLabel="Notes list options menu" isOpen={isOpen}>
|
||||
@@ -410,7 +421,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentMode === 'tag' && !isSystemTag && DailyEntryModeEnabled && (
|
||||
{currentMode === 'tag' && !isSystemTag && DailyEntryModeEnabled && !isTableViewEnabled && (
|
||||
<>
|
||||
<MenuItemSeparator />
|
||||
<MenuSwitchButtonItem
|
||||
@@ -432,6 +443,28 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentMode === 'tag' && !isSystemTag && !isDailyEntry && (
|
||||
<>
|
||||
<MenuItemSeparator />
|
||||
<MenuSwitchButtonItem
|
||||
disabled={controlsDisabled}
|
||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||
checked={isTableViewEnabled}
|
||||
onChange={toggleTableView}
|
||||
>
|
||||
<div className="flex flex-col pr-5">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="text-base font-semibold uppercase text-text lg:text-xs">Table view</div>
|
||||
<Pill className="py-0 px-1.5" style="success">
|
||||
Labs
|
||||
</Pill>
|
||||
</div>
|
||||
<div className="mt-1">Display the notes & files in the current tag in a table layout</div>
|
||||
</div>
|
||||
</MenuSwitchButtonItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!shouldHideNonApplicableOptions && (!isSystemTag || currentMode === 'global') && (
|
||||
<>
|
||||
<MenuItemSeparator />
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { AnyTag } from '@/Controllers/Navigation/AnyTagType'
|
||||
import { PaneController } from '@/Controllers/PaneController/PaneController'
|
||||
|
||||
export type DisplayOptionsMenuPositionProps = {
|
||||
top: number
|
||||
@@ -11,4 +12,5 @@ export type DisplayOptionsMenuProps = {
|
||||
selectedTag: AnyTag
|
||||
isOpen: boolean
|
||||
isFilesSmartView: boolean
|
||||
paneController: PaneController
|
||||
}
|
||||
|
||||
+210
-98
@@ -5,14 +5,14 @@ import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
||||
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
import {
|
||||
ContentType,
|
||||
FileItem,
|
||||
SortableItem,
|
||||
PrefKey,
|
||||
ApplicationEvent,
|
||||
naturalSort,
|
||||
FileBackupRecord,
|
||||
SystemViewId,
|
||||
DecryptedItemInterface,
|
||||
SNNote,
|
||||
} from '@standardnotes/snjs'
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
||||
import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
||||
@@ -24,18 +24,51 @@ import { useTable } from '../Table/useTable'
|
||||
import Menu from '../Menu/Menu'
|
||||
import FileMenuOptions from '../FileContextMenu/FileMenuOptions'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
|
||||
import LinkedItemBubble from '../LinkedItems/LinkedItemBubble'
|
||||
import LinkedItemsPanel from '../LinkedItems/LinkedItemsPanel'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType'
|
||||
import NotesOptions from '../NotesOptions/NotesOptions'
|
||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||
import { useItemLinks } from '@/Hooks/useItemLinks'
|
||||
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
||||
|
||||
const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesController: FilesController }) => {
|
||||
const ContextMenuCell = ({
|
||||
items,
|
||||
filesController,
|
||||
navigationController,
|
||||
linkingController,
|
||||
notesController,
|
||||
historyModalController,
|
||||
}: {
|
||||
items: DecryptedItemInterface[]
|
||||
filesController: FilesController
|
||||
navigationController: NavigationController
|
||||
linkingController: LinkingController
|
||||
notesController: NotesController
|
||||
historyModalController: HistoryModalController
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
const [contextMenuVisible, setContextMenuVisible] = useState(false)
|
||||
const anchorElementRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const allItemsAreNotes = useMemo(() => {
|
||||
return items.every((item) => item instanceof SNNote)
|
||||
}, [items])
|
||||
|
||||
const allItemsAreFiles = useMemo(() => {
|
||||
return items.every((item) => item instanceof FileItem)
|
||||
}, [items])
|
||||
|
||||
if (!allItemsAreNotes && !allItemsAreFiles) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -60,28 +93,45 @@ const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesC
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
|
||||
<FileMenuOptions
|
||||
closeMenu={() => {
|
||||
setContextMenuVisible(false)
|
||||
}}
|
||||
filesController={filesController}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
selectedFiles={files}
|
||||
/>
|
||||
{allItemsAreFiles && (
|
||||
<FileMenuOptions
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
closeMenu={() => {
|
||||
setContextMenuVisible(false)
|
||||
}}
|
||||
filesController={filesController}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
selectedFiles={items as FileItem[]}
|
||||
/>
|
||||
)}
|
||||
{allItemsAreNotes && (
|
||||
<NotesOptions
|
||||
notes={items as SNNote[]}
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
linkingController={linkingController}
|
||||
historyModalController={historyModalController}
|
||||
closeMenu={() => {
|
||||
setContextMenuVisible(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const FileLinksCell = ({
|
||||
file,
|
||||
const ItemLinksCell = ({
|
||||
item,
|
||||
filesController,
|
||||
linkingController,
|
||||
featuresController,
|
||||
}: {
|
||||
file: FileItem
|
||||
item: DecryptedItemInterface
|
||||
filesController: FilesController
|
||||
linkingController: LinkingController
|
||||
featuresController: FeaturesController
|
||||
@@ -117,25 +167,38 @@ const FileLinksCell = ({
|
||||
filesController={filesController}
|
||||
featuresController={featuresController}
|
||||
isOpen={contextMenuVisible}
|
||||
item={file}
|
||||
item={item}
|
||||
/>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const FileNameCell = ({ file }: { file: FileItem }) => {
|
||||
const ItemNameCell = ({ item }: { item: DecryptedItemInterface }) => {
|
||||
const application = useApplication()
|
||||
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
|
||||
const isItemFile = item instanceof FileItem
|
||||
|
||||
const noteType =
|
||||
item instanceof SNNote
|
||||
? item.noteType || application.componentManager.editorForNote(item)?.package_info.note_type
|
||||
: undefined
|
||||
const [noteIcon, noteIconTint] = getIconAndTintForNoteType(noteType)
|
||||
|
||||
useEffect(() => {
|
||||
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo)
|
||||
}, [application, file])
|
||||
if (isItemFile) {
|
||||
void application.fileBackups?.getFileBackupInfo(item).then(setBackupInfo)
|
||||
}
|
||||
}, [application, isItemFile, item])
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 whitespace-normal">
|
||||
<span className="relative">
|
||||
{getFileIconComponent(getIconForFileType(file.mimeType), 'w-6 h-6 flex-shrink-0')}
|
||||
{isItemFile ? (
|
||||
getFileIconComponent(getIconForFileType(item.mimeType), 'w-6 h-6 flex-shrink-0')
|
||||
) : (
|
||||
<Icon type={noteIcon} className={`text-accessory-tint-${noteIconTint}`} />
|
||||
)}
|
||||
{backupInfo && (
|
||||
<div
|
||||
className="absolute bottom-1 right-1 translate-x-1/2 translate-y-1/2 rounded-full bg-default text-success"
|
||||
@@ -145,8 +208,8 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{file.title}</span>
|
||||
{file.protected && (
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{item.title}</span>
|
||||
{item.protected && (
|
||||
<span className="flex items-center" title="File is protected">
|
||||
<Icon ariaLabel="File is protected" type="lock-filled" className="h-3.5 w-3.5 text-passive-1" size="custom" />
|
||||
</span>
|
||||
@@ -155,17 +218,60 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const AttachedToCell = ({ item }: { item: DecryptedItemInterface }) => {
|
||||
const { notesLinkedToItem, notesLinkingToItem, filesLinkedToItem, filesLinkingToItem, tagsLinkedToItem } =
|
||||
useItemLinks(item)
|
||||
const application = useApplication()
|
||||
|
||||
const allLinks: ItemLink[] = (notesLinkedToItem as ItemLink[]).concat(
|
||||
notesLinkingToItem,
|
||||
filesLinkedToItem,
|
||||
filesLinkingToItem,
|
||||
tagsLinkedToItem,
|
||||
)
|
||||
|
||||
if (!allLinks.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<LinkedItemBubble
|
||||
className="overflow-hidden border border-transparent hover:border-border focus:border-info focus:shadow-none"
|
||||
link={allLinks[0]}
|
||||
key={allLinks[0].id}
|
||||
unlinkItem={async (itemToUnlink) => {
|
||||
void application.items.unlinkItems(item, itemToUnlink)
|
||||
}}
|
||||
isBidirectional={false}
|
||||
/>
|
||||
{allLinks.length - 1 >= 1 && <span>and {allLinks.length - 1} more...</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
items: DecryptedItemInterface[]
|
||||
filesController: FilesController
|
||||
featuresController: FeaturesController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
notesController: NotesController
|
||||
historyModalController: HistoryModalController
|
||||
}
|
||||
|
||||
const FilesTableView = ({ application, filesController, featuresController, linkingController }: Props) => {
|
||||
const files = application.items
|
||||
.getDisplayableNotesAndFiles()
|
||||
.filter((item) => item.content_type === ContentType.File) as FileItem[]
|
||||
const ContentTableView = ({
|
||||
application,
|
||||
items,
|
||||
filesController,
|
||||
featuresController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
notesController,
|
||||
historyModalController,
|
||||
}: Props) => {
|
||||
const listHasFiles = items.some((item) => item instanceof FileItem)
|
||||
|
||||
const getSortByPreference = useCallback(() => {
|
||||
const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
|
||||
@@ -211,78 +317,49 @@ const FilesTableView = ({ application, filesController, featuresController, link
|
||||
[application],
|
||||
)
|
||||
|
||||
const [contextMenuFile, setContextMenuFile] = useState<FileItem | undefined>(undefined)
|
||||
const [contextMenuItem, setContextMenuItem] = useState<DecryptedItemInterface | undefined>(undefined)
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined)
|
||||
|
||||
const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const isMediumBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.md)
|
||||
const isLargeBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.lg)
|
||||
|
||||
const columnDefs: TableColumn<FileItem>[] = useMemo(
|
||||
const columnDefs: TableColumn<DecryptedItemInterface>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'Name',
|
||||
sortBy: 'title',
|
||||
cell: (file) => <FileNameCell file={file} />,
|
||||
cell: (item) => <ItemNameCell item={item} />,
|
||||
},
|
||||
{
|
||||
name: 'Upload date',
|
||||
sortBy: 'created_at',
|
||||
cell: (file) => {
|
||||
return formatDateForContextMenu(file.created_at)
|
||||
cell: (item) => {
|
||||
return formatDateForContextMenu(item.created_at)
|
||||
},
|
||||
hidden: isSmallBreakpoint,
|
||||
},
|
||||
{
|
||||
name: 'Size',
|
||||
sortBy: 'decryptedSize',
|
||||
cell: (file) => {
|
||||
return formatSizeToReadableString(file.decryptedSize)
|
||||
cell: (item) => {
|
||||
return item instanceof FileItem ? formatSizeToReadableString(item.decryptedSize) : null
|
||||
},
|
||||
hidden: isSmallBreakpoint,
|
||||
hidden: isSmallBreakpoint || !listHasFiles,
|
||||
},
|
||||
{
|
||||
name: 'Attached to',
|
||||
hidden: isSmallBreakpoint || isMediumBreakpoint || isLargeBreakpoint,
|
||||
cell: (file) => {
|
||||
const links = [
|
||||
...naturalSort(application.items.referencesForItem(file), 'title').map((item) =>
|
||||
createLinkFromItem(item, 'linked'),
|
||||
),
|
||||
...naturalSort(application.items.itemsReferencingItem(file), 'title').map((item) =>
|
||||
createLinkFromItem(item, 'linked-by'),
|
||||
),
|
||||
...application.items.getSortedTagsForItem(file).map((item) => createLinkFromItem(item, 'linked')),
|
||||
]
|
||||
|
||||
if (!links.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<LinkedItemBubble
|
||||
className="overflow-hidden border border-transparent hover:border-border focus:border-info focus:shadow-none"
|
||||
link={links[0]}
|
||||
key={links[0].id}
|
||||
unlinkItem={async (itemToUnlink) => {
|
||||
void application.items.unlinkItems(file, itemToUnlink)
|
||||
}}
|
||||
isBidirectional={false}
|
||||
/>
|
||||
{links.length - 1 > 1 && <span>and {links.length - 1} more...</span>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
cell: (item) => <AttachedToCell item={item} />,
|
||||
},
|
||||
],
|
||||
[application.items, isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint],
|
||||
[isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint, listHasFiles],
|
||||
)
|
||||
|
||||
const getRowId = useCallback((file: FileItem) => file.uuid, [])
|
||||
const getRowId = useCallback((item: DecryptedItemInterface) => item.uuid, [])
|
||||
|
||||
const table = useTable({
|
||||
data: files,
|
||||
data: items,
|
||||
sortBy,
|
||||
sortReversed,
|
||||
onSortChange,
|
||||
@@ -290,68 +367,103 @@ const FilesTableView = ({ application, filesController, featuresController, link
|
||||
columns: columnDefs,
|
||||
enableRowSelection: true,
|
||||
enableMultipleRowSelection: true,
|
||||
onRowDoubleClick(file) {
|
||||
void filesController.handleFileAction({
|
||||
type: FileItemActionType.PreviewFile,
|
||||
payload: {
|
||||
file,
|
||||
otherFiles: files,
|
||||
},
|
||||
})
|
||||
onRowActivate(item) {
|
||||
if (item instanceof FileItem) {
|
||||
void filesController.handleFileAction({
|
||||
type: FileItemActionType.PreviewFile,
|
||||
payload: {
|
||||
file: item,
|
||||
otherFiles: items.filter((i) => i instanceof FileItem) as FileItem[],
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
onRowContextMenu(x, y, file) {
|
||||
setContextMenuPosition({ x, y })
|
||||
setContextMenuFile(file)
|
||||
setContextMenuItem(file)
|
||||
},
|
||||
rowActions: (file) => {
|
||||
rowActions: (item) => {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<FileLinksCell
|
||||
file={file}
|
||||
<ItemLinksCell
|
||||
item={item}
|
||||
filesController={filesController}
|
||||
featuresController={featuresController}
|
||||
linkingController={linkingController}
|
||||
/>
|
||||
<ContextMenuCell files={[file]} filesController={filesController} />
|
||||
<ContextMenuCell
|
||||
items={[item]}
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
selectionActions: (fileIds) => (
|
||||
<ContextMenuCell files={files.filter((file) => fileIds.includes(file.uuid))} filesController={filesController} />
|
||||
selectionActions: (itemIds) => (
|
||||
<ContextMenuCell
|
||||
items={items.filter((item) => itemIds.includes(item.uuid))}
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
historyModalController={historyModalController}
|
||||
/>
|
||||
),
|
||||
showSelectionActions: true,
|
||||
})
|
||||
|
||||
const closeContextMenu = () => {
|
||||
setContextMenuPosition(undefined)
|
||||
setContextMenuItem(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table table={table} />
|
||||
{contextMenuPosition && contextMenuFile && (
|
||||
{contextMenuPosition && contextMenuItem && (
|
||||
<Popover
|
||||
open={true}
|
||||
anchorPoint={contextMenuPosition}
|
||||
togglePopover={() => {
|
||||
setContextMenuPosition(undefined)
|
||||
setContextMenuFile(undefined)
|
||||
setContextMenuItem(undefined)
|
||||
}}
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={true}>
|
||||
<FileMenuOptions
|
||||
closeMenu={() => {
|
||||
setContextMenuPosition(undefined)
|
||||
setContextMenuFile(undefined)
|
||||
}}
|
||||
filesController={filesController}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
selectedFiles={[contextMenuFile]}
|
||||
/>
|
||||
</Menu>
|
||||
{contextMenuItem instanceof FileItem && (
|
||||
<Menu a11yLabel="File context menu" isOpen={true}>
|
||||
<FileMenuOptions
|
||||
closeMenu={closeContextMenu}
|
||||
filesController={filesController}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
selectedFiles={[contextMenuItem]}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
{contextMenuItem instanceof SNNote && (
|
||||
<Menu className="select-none" a11yLabel="Note context menu" isOpen={true}>
|
||||
<NotesOptions
|
||||
notes={[contextMenuItem]}
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
linkingController={linkingController}
|
||||
historyModalController={historyModalController}
|
||||
closeMenu={closeContextMenu}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
</Popover>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default FilesTableView
|
||||
export default ContentTableView
|
||||
@@ -1,4 +1,6 @@
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
@@ -9,36 +11,47 @@ import FileMenuOptions from './FileMenuOptions'
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, selectionController }) => {
|
||||
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
const FileContextMenu: FunctionComponent<Props> = observer(
|
||||
({ filesController, selectionController, linkingController, navigationController }) => {
|
||||
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={showFileContextMenu}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectedFiles={selectedFiles}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<Popover
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={showFileContextMenu}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectedFiles={selectedFiles}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
FileContextMenu.displayName = 'FileContextMenu'
|
||||
|
||||
const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, selectionController }) => {
|
||||
const FileContextMenuWrapper: FunctionComponent<Props> = ({
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
selectionController,
|
||||
}) => {
|
||||
const { showFileContextMenu } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
|
||||
@@ -48,7 +61,14 @@ const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, sel
|
||||
return null
|
||||
}
|
||||
|
||||
return <FileContextMenu filesController={filesController} selectionController={selectionController} />
|
||||
return (
|
||||
<FileContextMenu
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectionController={selectionController}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(FileContextMenuWrapper)
|
||||
|
||||
@@ -11,10 +11,16 @@ import MenuItem from '../Menu/MenuItem'
|
||||
import { FileContextMenuBackupOption } from './FileContextMenuBackupOption'
|
||||
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import AddTagOption from '../NotesOptions/AddTagOption'
|
||||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
|
||||
type Props = {
|
||||
closeMenu: () => void
|
||||
filesController: FilesController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
isFileAttachedToNote?: boolean
|
||||
renameToggleCallback?: (isRenamingFile: boolean) => void
|
||||
shouldShowRenameOption: boolean
|
||||
@@ -25,6 +31,8 @@ type Props = {
|
||||
const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
closeMenu,
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
isFileAttachedToNote,
|
||||
renameToggleCallback,
|
||||
shouldShowRenameOption,
|
||||
@@ -82,6 +90,12 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
<AddTagOption
|
||||
navigationController={navigationController}
|
||||
linkingController={linkingController}
|
||||
selectedItems={selectedFiles}
|
||||
iconClassName={`text-neutral mr-2 ${MenuItemIconSize}`}
|
||||
/>
|
||||
<MenuSwitchButtonItem
|
||||
checked={hasProtectedFiles}
|
||||
onChange={(hasProtectedFiles) => {
|
||||
|
||||
@@ -6,13 +6,22 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import Popover from '../Popover/Popover'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
import Menu from '../Menu/Menu'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
const FilesOptionsPanel = ({
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
selectionController,
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -25,6 +34,8 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
<Menu a11yLabel="File options panel" isOpen={isOpen}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectedFiles={selectionController.selectedFiles}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
|
||||
@@ -10,6 +10,10 @@ import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import FilePreview from './FilePreview'
|
||||
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||
import FileMenuOptions from '../FileContextMenu/FileMenuOptions'
|
||||
import Menu from '../Menu/Menu'
|
||||
import Popover from '../Popover/Popover'
|
||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -23,7 +27,10 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
return null
|
||||
}
|
||||
|
||||
const [showLinkedBubblesContainer, setShowLinkedBubblesContainer] = useState(false)
|
||||
const [showOptionsMenu, setShowOptionsMenu] = useState(false)
|
||||
const [showFileInfoPanel, setShowFileInfoPanel] = useState(false)
|
||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const keyDownHandler: KeyboardEventHandler = useCallback(
|
||||
@@ -91,6 +98,44 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
<span className="ml-3 font-medium">{currentFile.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
onClick={() => setShowLinkedBubblesContainer((show) => !show)}
|
||||
>
|
||||
<Icon type="link" className="text-neutral" />
|
||||
</button>
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
onClick={() => setShowOptionsMenu((show) => !show)}
|
||||
ref={menuButtonRef}
|
||||
>
|
||||
<Icon type="more" className="text-neutral" />
|
||||
</button>
|
||||
<Popover
|
||||
open={showOptionsMenu}
|
||||
anchorElement={menuButtonRef.current}
|
||||
togglePopover={() => {
|
||||
setShowOptionsMenu(false)
|
||||
}}
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="py-2"
|
||||
overrideZIndex="z-modal"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={showOptionsMenu}>
|
||||
<FileMenuOptions
|
||||
filesController={viewControllerManager.filesController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
selectedFiles={[currentFile]}
|
||||
closeMenu={() => {
|
||||
setShowOptionsMenu(false)
|
||||
}}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
<button
|
||||
className="mr-4 flex cursor-pointer rounded border border-solid border-border bg-transparent p-1.5 hover:bg-contrast"
|
||||
onClick={() => setShowFileInfoPanel((show) => !show)}
|
||||
@@ -107,6 +152,14 @@ const FilePreviewModal: FunctionComponent<Props> = observer(({ application, view
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{showLinkedBubblesContainer && (
|
||||
<div className="-mt-1 border-b border-border py-1.5 px-3.5">
|
||||
<LinkedItemBubblesContainer
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
item={currentFile}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-h-0 flex-grow">
|
||||
<div className="relative flex max-w-full flex-grow items-center justify-center">
|
||||
<FilePreview file={currentFile} application={application} key={currentFile.uuid} />
|
||||
|
||||
@@ -112,10 +112,12 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
<FileOptionsPanel
|
||||
filesController={viewControllerManager.filesController}
|
||||
selectionController={viewControllerManager.selectionController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LinkedItemBubblesContainer linkingController={viewControllerManager.linkingController} />
|
||||
<LinkedItemBubblesContainer item={file} linkingController={viewControllerManager.linkingController} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-grow flex-col">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||
import { ElementIds } from '@/Constants/ElementIDs'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/snjs'
|
||||
import { ContentType, DecryptedItemInterface } from '@standardnotes/snjs'
|
||||
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
||||
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
||||
@@ -15,17 +15,18 @@ import { useItemLinks } from '@/Hooks/useItemLinks'
|
||||
|
||||
type Props = {
|
||||
linkingController: LinkingController
|
||||
item: DecryptedItemInterface
|
||||
}
|
||||
|
||||
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
||||
const LinkedItemBubblesContainer = ({ item, linkingController }: Props) => {
|
||||
const { toggleAppPane } = useResponsiveAppPane()
|
||||
|
||||
const commandService = useCommandService()
|
||||
|
||||
const { activeItem, unlinkItemFromSelectedItem: unlinkItem, activateItem } = linkingController
|
||||
const { unlinkItemFromSelectedItem: unlinkItem, activateItem } = linkingController
|
||||
|
||||
const { notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem, notesLinkingToItem, filesLinkingToItem } =
|
||||
useItemLinks(activeItem)
|
||||
useItemLinks(item)
|
||||
|
||||
const allItemsLinkedToItem: ItemLink[] = useMemo(
|
||||
() => new Array<ItemLink>().concat(notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem),
|
||||
@@ -97,10 +98,6 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
if (!activeItem) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -130,7 +127,7 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
||||
focusPreviousItem={focusPreviousItem}
|
||||
setFocusedId={setFocusedId}
|
||||
hoverLabel={`Focus input to add a link (${shortcut})`}
|
||||
item={activeItem}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
+16
-2
@@ -5,13 +5,22 @@ import { useCallback } from 'react'
|
||||
import FileOptionsPanel from '../FileContextMenu/FileOptionsPanel'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const MultipleSelectedFiles = ({ filesController, selectionController }: Props) => {
|
||||
const MultipleSelectedFiles = ({
|
||||
filesController,
|
||||
selectionController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
}: Props) => {
|
||||
const count = selectionController.selectedFilesCount
|
||||
|
||||
const cancelMultipleSelection = useCallback(() => {
|
||||
@@ -23,7 +32,12 @@ const MultipleSelectedFiles = ({ filesController, selectionController }: Props)
|
||||
<div className="flex w-full items-center justify-between p-4">
|
||||
<h1 className="m-0 text-lg font-bold">{count} selected files</h1>
|
||||
<div>
|
||||
<FileOptionsPanel filesController={filesController} selectionController={selectionController} />
|
||||
<FileOptionsPanel
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-full w-full max-w-md flex-grow flex-col items-center justify-center">
|
||||
|
||||
@@ -118,6 +118,8 @@ class NoteGroupView extends AbstractComponent<Props, State> {
|
||||
<MultipleSelectedFiles
|
||||
filesController={this.viewControllerManager.filesController}
|
||||
selectionController={this.viewControllerManager.selectionController}
|
||||
navigationController={this.viewControllerManager.navigationController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
/>
|
||||
)}
|
||||
{shouldNotShowMultipleSelectedItems && hasControllers && (
|
||||
|
||||
@@ -905,7 +905,10 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||
<LinkedItemBubblesContainer
|
||||
item={this.note}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ const NotesContextMenu = ({
|
||||
>
|
||||
<Menu className="select-none" a11yLabel="Note context menu" isOpen={contextMenuOpen}>
|
||||
<NotesOptions
|
||||
notes={notesController.selectedNotes}
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
|
||||
@@ -2,22 +2,28 @@ import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import Popover from '../Popover/Popover'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import { classNames, DecryptedItemInterface, IconType, SNTag } from '@standardnotes/snjs'
|
||||
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import MenuItem from '../Menu/MenuItem'
|
||||
import Menu from '../Menu/Menu'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
|
||||
type Props = {
|
||||
navigationController: NavigationController
|
||||
notesController: NotesController
|
||||
linkingController: LinkingController
|
||||
selectedItems: DecryptedItemInterface[]
|
||||
iconClassName: string
|
||||
}
|
||||
|
||||
const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, iconClassName }) => {
|
||||
const AddTagOption: FunctionComponent<Props> = ({
|
||||
navigationController,
|
||||
linkingController,
|
||||
selectedItems,
|
||||
iconClassName,
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -28,6 +34,18 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
const isTagLinkedToSelectedItems = (tag: SNTag) => {
|
||||
return selectedItems.every((item) => application.getItemTags(item).find((itemTag) => itemTag.uuid === tag.uuid))
|
||||
}
|
||||
|
||||
const linkTagToSelectedItems = (tag: SNTag) => {
|
||||
selectedItems.forEach((item) => linkingController.linkItems(item, tag))
|
||||
}
|
||||
|
||||
const unlinkTagFromSelectedItems = (tag: SNTag) => {
|
||||
selectedItems.forEach((item) => linkingController.unlinkItems(item, tag))
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<MenuItem
|
||||
@@ -53,15 +71,14 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
side="right"
|
||||
align="start"
|
||||
className="py-2"
|
||||
overrideZIndex="z-modal"
|
||||
>
|
||||
<Menu a11yLabel="Tag selection menu" isOpen={isOpen}>
|
||||
{navigationController.tags.map((tag) => (
|
||||
<MenuItem
|
||||
key={tag.uuid}
|
||||
onClick={() => {
|
||||
notesController.isTagInSelectedNotes(tag)
|
||||
? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
|
||||
: notesController.addTagToSelectedNotes(tag).catch(console.error)
|
||||
isTagLinkedToSelectedItems(tag) ? unlinkTagFromSelectedItems(tag) : linkTagToSelectedItems(tag)
|
||||
}}
|
||||
>
|
||||
{tag.iconString && (
|
||||
@@ -72,8 +89,10 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={`overflow-hidden overflow-ellipsis whitespace-nowrap
|
||||
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}
|
||||
className={classNames(
|
||||
'overflow-hidden overflow-ellipsis whitespace-nowrap',
|
||||
isTagLinkedToSelectedItems(tag) ? 'font-bold' : '',
|
||||
)}
|
||||
>
|
||||
{getTitleForLinkedTag(tag, application)?.longTitle}
|
||||
</span>
|
||||
|
||||
@@ -39,9 +39,11 @@ const iconClassWarning = `text-warning mr-2 ${iconSize}`
|
||||
const iconClassSuccess = `text-success mr-2 ${iconSize}`
|
||||
|
||||
const NotesOptions = ({
|
||||
notes,
|
||||
application,
|
||||
navigationController,
|
||||
notesController,
|
||||
linkingController,
|
||||
historyModalController,
|
||||
closeMenu,
|
||||
}: NotesOptionsProps) => {
|
||||
@@ -60,7 +62,6 @@ const NotesOptions = ({
|
||||
return notesMatchingAttribute.length > notesNotMatchingAttribute.length
|
||||
}
|
||||
|
||||
const notes = notesController.selectedNotes
|
||||
const hidePreviews = toggleOn((note) => note.hidePreview)
|
||||
const locked = toggleOn((note) => note.locked)
|
||||
const protect = toggleOn((note) => note.protected)
|
||||
@@ -214,7 +215,8 @@ const NotesOptions = ({
|
||||
<AddTagOption
|
||||
iconClassName={iconClass}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
selectedItems={notes}
|
||||
linkingController={linkingController}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
|
||||
@@ -55,6 +55,7 @@ const NotesOptionsPanel = ({
|
||||
>
|
||||
<Menu a11yLabel="Note options menu" isOpen={isOpen}>
|
||||
<NotesOptions
|
||||
notes={notesController.selectedNotes}
|
||||
application={application}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
|
||||
@@ -3,8 +3,10 @@ import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalCo
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { SNNote } from '@standardnotes/snjs'
|
||||
|
||||
export type NotesOptionsProps = {
|
||||
notes: SNNote[]
|
||||
application: WebApplication
|
||||
navigationController: NavigationController
|
||||
notesController: NotesController
|
||||
|
||||
@@ -304,6 +304,8 @@ const PanesSystemComponent = () => {
|
||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
featuresController={viewControllerManager.featuresController}
|
||||
historyModalController={viewControllerManager.historyModalController}
|
||||
paneController={viewControllerManager.paneController}
|
||||
>
|
||||
{showPanelResizers && listRef && (
|
||||
<PanelResizer
|
||||
|
||||
@@ -35,12 +35,15 @@ export type TableHeader = {
|
||||
}
|
||||
|
||||
export type Table<Data> = {
|
||||
id: string
|
||||
headers: TableHeader[]
|
||||
rows: TableRow<Data>[]
|
||||
rowCount: number
|
||||
colCount: number
|
||||
handleRowClick: (id: string) => MouseEventHandler<HTMLTableRowElement>
|
||||
handleRowDoubleClick: (id: string) => MouseEventHandler<HTMLTableRowElement>
|
||||
selectRow: (id: string) => void
|
||||
multiSelectRow: (id: string) => void
|
||||
rangeSelectUpToRow: (id: string) => void
|
||||
handleActivateRow: (id: string) => void
|
||||
handleRowContextMenu: (id: string) => MouseEventHandler<HTMLTableRowElement>
|
||||
canSelectRows: boolean
|
||||
canSelectMultipleRows: boolean
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { useCallback, useState, useRef } from 'react'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { Table, TableRow } from './CommonTypes'
|
||||
|
||||
@@ -9,22 +11,25 @@ function TableRow<Data>({
|
||||
canSelectRows,
|
||||
handleRowClick,
|
||||
handleRowContextMenu,
|
||||
handleRowDoubleClick,
|
||||
handleActivateRow,
|
||||
}: {
|
||||
row: TableRow<Data>
|
||||
index: number
|
||||
canSelectRows: Table<Data>['canSelectRows']
|
||||
handleRowClick: Table<Data>['handleRowClick']
|
||||
handleRowClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, id: string) => void
|
||||
handleRowContextMenu: Table<Data>['handleRowContextMenu']
|
||||
handleRowDoubleClick: Table<Data>['handleRowDoubleClick']
|
||||
handleActivateRow: Table<Data>['handleActivateRow']
|
||||
}) {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const isHoveredOrFocused = isHovered || isFocused
|
||||
|
||||
const visibleCells = row.cells.filter((cell) => !cell.hidden)
|
||||
|
||||
return (
|
||||
<div
|
||||
role="row"
|
||||
id={row.id}
|
||||
aria-rowindex={rowIndex + 2}
|
||||
{...(canSelectRows ? { 'aria-selected': row.isSelected } : {})}
|
||||
className="group relative contents"
|
||||
@@ -34,9 +39,17 @@ function TableRow<Data>({
|
||||
onMouseLeave={() => {
|
||||
setIsHovered(false)
|
||||
}}
|
||||
onClick={handleRowClick(row.id)}
|
||||
onDoubleClick={handleRowDoubleClick(row.id)}
|
||||
onClick={(event) => handleRowClick(event, row.id)}
|
||||
onDoubleClick={() => handleActivateRow(row.id)}
|
||||
onContextMenu={handleRowContextMenu(row.id)}
|
||||
onFocus={() => {
|
||||
setIsFocused(true)
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
if (!event.relatedTarget?.closest(`[id="${row.id}"]`)) {
|
||||
setIsFocused(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{visibleCells.map((cell, index, array) => {
|
||||
return (
|
||||
@@ -46,25 +59,27 @@ function TableRow<Data>({
|
||||
aria-colindex={cell.colIndex + 1}
|
||||
key={index}
|
||||
className={classNames(
|
||||
'relative flex items-center overflow-hidden border-b border-border py-3 px-3',
|
||||
'relative flex items-center overflow-hidden border-b border-border py-3 px-3 focus:border-info',
|
||||
row.isSelected && 'bg-info-backdrop',
|
||||
canSelectRows && 'cursor-pointer',
|
||||
canSelectRows && isHovered && 'bg-contrast',
|
||||
canSelectRows && isHoveredOrFocused && 'bg-contrast',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{cell.render}
|
||||
{row.rowActions && index === array.length - 1 && (
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute right-0 top-0 flex h-full items-center p-2',
|
||||
row.isSelected ? '' : isHovered ? '' : 'invisible',
|
||||
row.isSelected ? '' : isHoveredOrFocused ? '' : 'invisible',
|
||||
isFocused && 'visible',
|
||||
)}
|
||||
>
|
||||
<div className="z-[1]">{row.rowActions}</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-0 right-0 z-0 h-full w-full backdrop-blur-[2px]',
|
||||
row.isSelected ? '' : isHovered ? '' : 'invisible',
|
||||
row.isSelected ? '' : isHoveredOrFocused ? '' : 'invisible',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -82,6 +97,8 @@ const PageSize = Math.ceil(document.documentElement.clientHeight / MinTableRowHe
|
||||
const PageScrollThreshold = 200
|
||||
|
||||
function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
const application = useApplication()
|
||||
|
||||
const [rowsToDisplay, setRowsToDisplay] = useState<number>(PageSize)
|
||||
const paginate = useCallback(() => {
|
||||
setRowsToDisplay((cellsToDisplay) => cellsToDisplay + PageSize)
|
||||
@@ -98,13 +115,16 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
)
|
||||
|
||||
const {
|
||||
id,
|
||||
headers,
|
||||
rows,
|
||||
colCount,
|
||||
rowCount,
|
||||
handleRowClick,
|
||||
selectRow,
|
||||
multiSelectRow,
|
||||
rangeSelectUpToRow,
|
||||
handleRowContextMenu,
|
||||
handleRowDoubleClick,
|
||||
handleActivateRow,
|
||||
selectedRows,
|
||||
selectionActions,
|
||||
canSelectRows,
|
||||
@@ -112,10 +132,236 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
showSelectionActions,
|
||||
} = table
|
||||
|
||||
const focusedRowIndex = useRef<number>(0)
|
||||
const focusedCellIndex = useRef<number>(0)
|
||||
|
||||
const onFocus: React.FocusEventHandler = useCallback((event) => {
|
||||
const target = event.target as HTMLElement
|
||||
const row = target.closest('[role="row"]') as HTMLElement
|
||||
const cell = target.closest('[role="gridcell"],[role="columnheader"]') as HTMLElement
|
||||
if (row) {
|
||||
focusedRowIndex.current = parseInt(row.getAttribute('aria-rowindex') || '0')
|
||||
}
|
||||
if (cell) {
|
||||
focusedCellIndex.current = parseInt(cell.getAttribute('aria-colindex') || '0')
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onBlur: React.FocusEventHandler = useCallback((event) => {
|
||||
const activeElement = document.activeElement as HTMLElement
|
||||
if (activeElement.closest('[role="grid"]') !== event.target) {
|
||||
focusedRowIndex.current = 0
|
||||
focusedCellIndex.current = 0
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onKeyDown: React.KeyboardEventHandler = useCallback(
|
||||
(event) => {
|
||||
const gridElement = event.currentTarget
|
||||
const allRenderedRows = gridElement.querySelectorAll<HTMLElement>('[role="row"]')
|
||||
const currentRow = Array.from(allRenderedRows).find(
|
||||
(row) => row.getAttribute('aria-rowindex') === focusedRowIndex.current.toString(),
|
||||
)
|
||||
const allFocusableCells = Array.from(currentRow ? currentRow.querySelectorAll<HTMLElement>('[tabindex]') : [])
|
||||
const allRenderedColumnsLength = headers.length
|
||||
|
||||
const focusCell = (rowIndex: number, colIndex: number) => {
|
||||
const row = gridElement.querySelector(`[role="row"][aria-rowindex="${rowIndex}"]`)
|
||||
if (!row) {
|
||||
return
|
||||
}
|
||||
const cell = row.querySelector<HTMLElement>(`[aria-colindex="${colIndex}"]`)
|
||||
if (cell) {
|
||||
cell.focus()
|
||||
}
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case KeyboardKey.Up:
|
||||
event.preventDefault()
|
||||
if (focusedRowIndex.current > 1) {
|
||||
const previousRow = focusedRowIndex.current - 1
|
||||
focusCell(previousRow, focusedCellIndex.current)
|
||||
}
|
||||
break
|
||||
case KeyboardKey.Down:
|
||||
event.preventDefault()
|
||||
if (focusedRowIndex.current <= rowCount) {
|
||||
const nextRow = focusedRowIndex.current + 1
|
||||
focusCell(nextRow, focusedCellIndex.current)
|
||||
}
|
||||
break
|
||||
case KeyboardKey.Left: {
|
||||
event.preventDefault()
|
||||
if (!allFocusableCells) {
|
||||
return
|
||||
}
|
||||
const currentCellIndex = allFocusableCells.findIndex(
|
||||
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
|
||||
)
|
||||
if (currentCellIndex === 0) {
|
||||
return
|
||||
}
|
||||
const previousCell = allFocusableCells[currentCellIndex - 1]
|
||||
if (!previousCell) {
|
||||
return
|
||||
}
|
||||
previousCell.focus()
|
||||
break
|
||||
}
|
||||
case KeyboardKey.Right: {
|
||||
event.preventDefault()
|
||||
if (!allFocusableCells) {
|
||||
return
|
||||
}
|
||||
const currentCellIndex = allFocusableCells.findIndex(
|
||||
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
|
||||
)
|
||||
if (currentCellIndex === allFocusableCells.length - 1) {
|
||||
return
|
||||
}
|
||||
const nextCell = allFocusableCells[currentCellIndex + 1]
|
||||
if (!nextCell) {
|
||||
return
|
||||
}
|
||||
nextCell.focus()
|
||||
break
|
||||
}
|
||||
case KeyboardKey.Home:
|
||||
event.preventDefault()
|
||||
if (event.ctrlKey) {
|
||||
focusCell(1, 1)
|
||||
} else {
|
||||
if (!allFocusableCells) {
|
||||
return
|
||||
}
|
||||
const firstFocusableCell = allFocusableCells[0]
|
||||
if (!firstFocusableCell) {
|
||||
return
|
||||
}
|
||||
const firstCellIndex = parseInt(firstFocusableCell.getAttribute('aria-colindex') || '0')
|
||||
if (firstCellIndex > 0) {
|
||||
focusCell(focusedRowIndex.current, firstCellIndex)
|
||||
}
|
||||
}
|
||||
break
|
||||
case KeyboardKey.End: {
|
||||
event.preventDefault()
|
||||
if (event.ctrlKey) {
|
||||
focusCell(allRenderedRows.length, allRenderedColumnsLength || colCount)
|
||||
return
|
||||
}
|
||||
if (!allFocusableCells) {
|
||||
return
|
||||
}
|
||||
const lastFocusableCell = allFocusableCells[allFocusableCells.length - 1]
|
||||
if (!lastFocusableCell) {
|
||||
return
|
||||
}
|
||||
const lastCellIndex = parseInt(lastFocusableCell.getAttribute('aria-colindex') || '0')
|
||||
if (lastCellIndex > 0) {
|
||||
focusCell(focusedRowIndex.current, lastCellIndex)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardKey.PageUp: {
|
||||
event.preventDefault()
|
||||
const previousRow = focusedRowIndex.current - 5
|
||||
if (previousRow > 0) {
|
||||
focusCell(previousRow, focusedCellIndex.current)
|
||||
} else {
|
||||
focusCell(1, focusedCellIndex.current)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardKey.PageDown: {
|
||||
event.preventDefault()
|
||||
const nextRow = focusedRowIndex.current + 5
|
||||
if (nextRow <= allRenderedRows.length) {
|
||||
focusCell(nextRow, focusedCellIndex.current)
|
||||
} else {
|
||||
focusCell(allRenderedRows.length, focusedCellIndex.current)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardKey.Enter: {
|
||||
const target = event.target as HTMLElement
|
||||
const closestColumnHeader = target.closest<HTMLElement>('[role="columnheader"]')
|
||||
if (closestColumnHeader && closestColumnHeader.getAttribute('data-can-sort')) {
|
||||
event.preventDefault()
|
||||
closestColumnHeader.click()
|
||||
return
|
||||
}
|
||||
const currentRowId = currentRow?.id
|
||||
if (currentRowId) {
|
||||
event.preventDefault()
|
||||
handleActivateRow(currentRowId)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardKey.Space: {
|
||||
const target = event.target as HTMLElement
|
||||
const currentRowId = currentRow?.id
|
||||
if (!currentRowId) {
|
||||
return
|
||||
}
|
||||
if (target.getAttribute('role') !== 'gridcell') {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
const isCmdOrCtrlPressed = application.keyboardService.isMac ? event.metaKey : event.ctrlKey
|
||||
if (isCmdOrCtrlPressed && canSelectMultipleRows) {
|
||||
multiSelectRow(currentRowId)
|
||||
} else if (event.shiftKey && canSelectMultipleRows) {
|
||||
rangeSelectUpToRow(currentRowId)
|
||||
} else {
|
||||
selectRow(currentRowId)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
application.keyboardService.isMac,
|
||||
canSelectMultipleRows,
|
||||
colCount,
|
||||
handleActivateRow,
|
||||
headers.length,
|
||||
multiSelectRow,
|
||||
rangeSelectUpToRow,
|
||||
rowCount,
|
||||
selectRow,
|
||||
],
|
||||
)
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(event: React.MouseEvent, rowId: string) => {
|
||||
if (!canSelectRows) {
|
||||
return
|
||||
}
|
||||
const isCmdOrCtrlPressed = application.keyboardService.isMac ? event.metaKey : event.ctrlKey
|
||||
if (isCmdOrCtrlPressed && canSelectMultipleRows) {
|
||||
multiSelectRow(rowId)
|
||||
} else if (event.shiftKey && canSelectMultipleRows) {
|
||||
rangeSelectUpToRow(rowId)
|
||||
} else {
|
||||
selectRow(rowId)
|
||||
}
|
||||
},
|
||||
[
|
||||
application.keyboardService.isMac,
|
||||
canSelectMultipleRows,
|
||||
canSelectRows,
|
||||
multiSelectRow,
|
||||
rangeSelectUpToRow,
|
||||
selectRow,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="block min-h-0 overflow-auto" onScroll={onScroll}>
|
||||
{showSelectionActions && selectedRows.length >= 2 && (
|
||||
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
||||
<div className="sticky top-0 z-[2] flex items-center justify-between border-b border-border bg-default px-3 py-2">
|
||||
<span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span>
|
||||
{selectedRows.length > 0 && selectionActions}
|
||||
</div>
|
||||
@@ -126,6 +372,10 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
aria-colcount={colCount}
|
||||
aria-rowcount={rowCount}
|
||||
aria-multiselectable={canSelectMultipleRows}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
id={`table-${id}`}
|
||||
>
|
||||
<div role="row" aria-rowindex={1} className="contents">
|
||||
{headers
|
||||
@@ -139,13 +389,16 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
aria-sort={header.isSorting ? (header.sortReversed ? 'descending' : 'ascending') : 'none'}
|
||||
className={classNames(
|
||||
'border-b border-border px-3 pt-3 pb-2 text-left text-sm font-medium text-passive-0',
|
||||
header.sortBy && 'cursor-pointer hover:bg-info-backdrop hover:underline',
|
||||
header.sortBy &&
|
||||
'cursor-pointer hover:bg-info-backdrop hover:underline focus:border-info focus:bg-info-backdrop',
|
||||
)}
|
||||
style={{
|
||||
gridColumn: index + 1,
|
||||
}}
|
||||
onClick={header.onSortChange}
|
||||
key={index.toString()}
|
||||
data-can-sort={header.sortBy ? true : undefined}
|
||||
{...(header.sortBy && { tabIndex: index === 0 ? 0 : -1 })}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{header.name}
|
||||
@@ -170,7 +423,7 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
||||
canSelectRows={canSelectRows}
|
||||
handleRowClick={handleRowClick}
|
||||
handleRowContextMenu={handleRowContextMenu}
|
||||
handleRowDoubleClick={handleRowDoubleClick}
|
||||
handleActivateRow={handleActivateRow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MouseEventHandler, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { UuidGenerator } from '@standardnotes/snjs'
|
||||
import { MouseEventHandler, ReactNode, useCallback, useEffect, useMemo, useState, useRef } from 'react'
|
||||
import { Table, TableColumn, TableHeader, TableRow, TableSortBy } from './CommonTypes'
|
||||
|
||||
type TableSortOptions =
|
||||
@@ -34,7 +34,7 @@ type TableSelectionOptions =
|
||||
|
||||
type TableRowOptions<Data> = {
|
||||
getRowId?: (data: Data) => string
|
||||
onRowDoubleClick?: (data: Data) => void
|
||||
onRowActivate?: (data: Data) => void
|
||||
onRowContextMenu?: (x: number, y: number, data: Data) => void
|
||||
rowActions?: (data: Data) => ReactNode
|
||||
}
|
||||
@@ -57,14 +57,14 @@ export function useTable<Data>({
|
||||
enableMultipleRowSelection,
|
||||
selectedRowIds,
|
||||
onRowSelectionChange,
|
||||
onRowDoubleClick,
|
||||
onRowActivate,
|
||||
onRowContextMenu,
|
||||
rowActions,
|
||||
selectionActions,
|
||||
showSelectionActions,
|
||||
}: UseTableOptions<Data>): Table<Data> {
|
||||
const application = useApplication()
|
||||
const [selectedRows, setSelectedRows] = useState<string[]>(selectedRowIds || [])
|
||||
const id = useRef(UuidGenerator.GenerateUuid())
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRowIds) {
|
||||
@@ -122,45 +122,55 @@ export function useTable<Data>({
|
||||
[columns, data, enableRowSelection, getRowId, rowActions, selectedRows],
|
||||
)
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
const selectRow = useCallback(
|
||||
(id: string) => {
|
||||
const handler: MouseEventHandler<HTMLTableRowElement> = (event) => {
|
||||
if (!enableRowSelection) {
|
||||
return
|
||||
}
|
||||
const isCmdOrCtrlPressed = application.keyboardService.isMac ? event.metaKey : event.ctrlKey
|
||||
if (isCmdOrCtrlPressed && enableMultipleRowSelection) {
|
||||
setSelectedRows((prev) => (prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id]))
|
||||
} else if (event.shiftKey && enableMultipleRowSelection) {
|
||||
const lastSelectedIndex = rows.findIndex((row) => row.id === selectedRows[selectedRows.length - 1])
|
||||
const currentIndex = rows.findIndex((row) => row.id === id)
|
||||
const start = Math.min(lastSelectedIndex, currentIndex)
|
||||
const end = Math.max(lastSelectedIndex, currentIndex)
|
||||
const newSelectedRows = rows.slice(start, end + 1).map((row) => row.id)
|
||||
setSelectedRows(newSelectedRows)
|
||||
} else {
|
||||
setSelectedRows([id])
|
||||
}
|
||||
if (!enableRowSelection) {
|
||||
return
|
||||
}
|
||||
return handler
|
||||
|
||||
setSelectedRows([id])
|
||||
},
|
||||
[application.keyboardService.isMac, enableMultipleRowSelection, enableRowSelection, rows, selectedRows],
|
||||
[enableRowSelection],
|
||||
)
|
||||
|
||||
const handleRowDoubleClick = useCallback(
|
||||
const multiSelectRow = useCallback(
|
||||
(id: string) => {
|
||||
const handler: MouseEventHandler<HTMLTableRowElement> = () => {
|
||||
if (!onRowDoubleClick) {
|
||||
return
|
||||
}
|
||||
const rowData = rows.find((row) => row.id === id)?.rowData
|
||||
if (rowData) {
|
||||
onRowDoubleClick(rowData)
|
||||
}
|
||||
if (!enableRowSelection || !enableMultipleRowSelection) {
|
||||
return
|
||||
}
|
||||
return handler
|
||||
|
||||
setSelectedRows((prev) => (prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id]))
|
||||
},
|
||||
[onRowDoubleClick, rows],
|
||||
[enableMultipleRowSelection, enableRowSelection],
|
||||
)
|
||||
|
||||
const rangeSelectUpToRow = useCallback(
|
||||
(id: string) => {
|
||||
if (!enableRowSelection || !enableMultipleRowSelection) {
|
||||
return
|
||||
}
|
||||
|
||||
const lastSelectedIndex = rows.findIndex((row) => row.id === selectedRows[selectedRows.length - 1])
|
||||
const currentIndex = rows.findIndex((row) => row.id === id)
|
||||
const start = Math.min(lastSelectedIndex, currentIndex)
|
||||
const end = Math.max(lastSelectedIndex, currentIndex)
|
||||
const newSelectedRows = rows.slice(start, end + 1).map((row) => row.id)
|
||||
setSelectedRows(newSelectedRows)
|
||||
},
|
||||
[enableMultipleRowSelection, enableRowSelection, rows, selectedRows],
|
||||
)
|
||||
|
||||
const handleActivateRow = useCallback(
|
||||
(id: string) => {
|
||||
if (!onRowActivate) {
|
||||
return
|
||||
}
|
||||
const rowData = rows.find((row) => row.id === id)?.rowData
|
||||
if (rowData) {
|
||||
onRowActivate(rowData)
|
||||
}
|
||||
},
|
||||
[onRowActivate, rows],
|
||||
)
|
||||
|
||||
const handleRowContextMenu = useCallback(
|
||||
@@ -186,12 +196,15 @@ export function useTable<Data>({
|
||||
|
||||
const table: Table<Data> = useMemo(
|
||||
() => ({
|
||||
id: id.current,
|
||||
headers,
|
||||
rows,
|
||||
colCount,
|
||||
rowCount,
|
||||
handleRowClick,
|
||||
handleRowDoubleClick,
|
||||
selectRow,
|
||||
multiSelectRow,
|
||||
rangeSelectUpToRow,
|
||||
handleActivateRow,
|
||||
handleRowContextMenu,
|
||||
selectedRows,
|
||||
canSelectRows: enableRowSelection || false,
|
||||
@@ -200,16 +213,18 @@ export function useTable<Data>({
|
||||
showSelectionActions: showSelectionActions || false,
|
||||
}),
|
||||
[
|
||||
colCount,
|
||||
enableMultipleRowSelection,
|
||||
enableRowSelection,
|
||||
handleRowClick,
|
||||
handleRowContextMenu,
|
||||
handleRowDoubleClick,
|
||||
headers,
|
||||
rowCount,
|
||||
rows,
|
||||
colCount,
|
||||
rowCount,
|
||||
selectRow,
|
||||
multiSelectRow,
|
||||
rangeSelectUpToRow,
|
||||
handleActivateRow,
|
||||
handleRowContextMenu,
|
||||
selectedRows,
|
||||
enableRowSelection,
|
||||
enableMultipleRowSelection,
|
||||
selectionActions,
|
||||
showSelectionActions,
|
||||
],
|
||||
|
||||
@@ -11,15 +11,17 @@ import { ItemListController } from './ItemListController'
|
||||
import { ItemsReloadSource } from './ItemsReloadSource'
|
||||
|
||||
describe('item list controller', () => {
|
||||
let application: WebApplication
|
||||
let controller: ItemListController
|
||||
let navigationController: NavigationController
|
||||
let selectionController: SelectedItemsController
|
||||
|
||||
beforeEach(() => {
|
||||
const application = {} as jest.Mocked<WebApplication>
|
||||
application = {} as jest.Mocked<WebApplication>
|
||||
application.streamItems = jest.fn()
|
||||
application.addEventObserver = jest.fn()
|
||||
application.addWebEventObserver = jest.fn()
|
||||
application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
|
||||
|
||||
navigationController = {} as jest.Mocked<NavigationController>
|
||||
selectionController = {} as jest.Mocked<SelectedItemsController>
|
||||
@@ -50,6 +52,12 @@ describe('item list controller', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should return false is platform is native mobile web', () => {
|
||||
application.isNativeMobileWeb = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(controller.shouldSelectFirstItem(ItemsReloadSource.TagChange)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false first item is file', () => {
|
||||
controller.getFirstNonProtectedItem = jest.fn().mockReturnValue({
|
||||
content_type: ContentType.File,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { destroyAllObjectProperties, isMobileScreen } from '@/Utils'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
CollectionSort,
|
||||
@@ -71,6 +71,7 @@ export class ItemListController extends AbstractViewController implements Intern
|
||||
hideNotePreview: false,
|
||||
hideEditorIcon: false,
|
||||
}
|
||||
isTableViewEnabled = false
|
||||
private reloadItemsPromise?: Promise<unknown>
|
||||
|
||||
override deinit() {
|
||||
@@ -414,6 +415,10 @@ export class ItemListController extends AbstractViewController implements Intern
|
||||
}
|
||||
|
||||
shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource) => {
|
||||
if (this.application.isNativeMobileWeb()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const item = this.getFirstNonProtectedItem()
|
||||
if (item && isFile(item)) {
|
||||
return false
|
||||
@@ -447,6 +452,10 @@ export class ItemListController extends AbstractViewController implements Intern
|
||||
this.selectionController.deselectItem(activeItem)
|
||||
|
||||
if (this.shouldSelectFirstItem(itemsReloadSource)) {
|
||||
if (this.isTableViewEnabled && !isMobileScreen()) {
|
||||
return
|
||||
}
|
||||
|
||||
log(LoggingDomain.Selection, 'Selecting next item after closing active one')
|
||||
this.selectionController.selectNextItem({ userTriggered: false })
|
||||
}
|
||||
@@ -509,6 +518,8 @@ export class ItemListController extends AbstractViewController implements Intern
|
||||
? this.application.getPreference(PrefKey.SystemViewPreferences)?.[selectedTag.uuid as SystemViewId]
|
||||
: selectedTag?.preferences
|
||||
|
||||
this.isTableViewEnabled = Boolean(selectedTagPreferences?.useTableView)
|
||||
|
||||
const currentSortBy = this.displayOptions.sortBy
|
||||
let sortBy =
|
||||
selectedTagPreferences?.sortBy ||
|
||||
@@ -725,6 +736,10 @@ export class ItemListController extends AbstractViewController implements Intern
|
||||
selectFirstItem = async () => {
|
||||
const item = this.getFirstNonProtectedItem()
|
||||
|
||||
if (this.isTableViewEnabled && !isMobileScreen()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item) {
|
||||
log(LoggingDomain.Selection, 'Selecting first item', item.uuid)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
InternalEventPublishStrategy,
|
||||
VectorIconNameOrEmoji,
|
||||
isTag,
|
||||
PrefKey,
|
||||
} from '@standardnotes/snjs'
|
||||
import { action, computed, makeAutoObservable, makeObservable, observable, reaction, runInAction } from 'mobx'
|
||||
import { WebApplication } from '../../Application/Application'
|
||||
@@ -196,10 +197,13 @@ export class NavigationController
|
||||
}
|
||||
|
||||
hydrateFromPersistedValue = (state: NavigationControllerPersistableValue | undefined) => {
|
||||
if (!state) {
|
||||
const uuidsToPreventHydrationOf: string[] = [SystemViewId.Files]
|
||||
|
||||
if (!state || uuidsToPreventHydrationOf.includes(state.selectedTagUuid)) {
|
||||
void this.selectHomeNavigationView()
|
||||
return
|
||||
}
|
||||
|
||||
if (state.selectedTagUuid) {
|
||||
this.selectedUuid = state.selectedTagUuid
|
||||
this.selectHydratedTagOrDefault()
|
||||
@@ -265,6 +269,14 @@ export class NavigationController
|
||||
return tag.uuid === SystemViewId.Files
|
||||
}
|
||||
|
||||
tagUsesTableView(tag: AnyTag): boolean {
|
||||
const isSystemView = tag instanceof SmartView && Object.values(SystemViewId).includes(tag.uuid as SystemViewId)
|
||||
const useTableView = isSystemView
|
||||
? this.application.getPreference(PrefKey.SystemViewPreferences)?.[tag.uuid as SystemViewId]
|
||||
: tag?.preferences
|
||||
return Boolean(useTableView)
|
||||
}
|
||||
|
||||
public isInAnySystemView(): boolean {
|
||||
return (
|
||||
this.selected instanceof SmartView && Object.values(SystemViewId).includes(this.selected.uuid as SystemViewId)
|
||||
@@ -463,8 +475,8 @@ export class NavigationController
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
if (tag && this.isTagFilesView(tag)) {
|
||||
this.application.paneController.setPaneLayout(PaneLayout.FilesView)
|
||||
if (tag && (this.isTagFilesView(tag) || this.tagUsesTableView(tag))) {
|
||||
this.application.paneController.setPaneLayout(PaneLayout.TableView)
|
||||
} else if (userTriggered) {
|
||||
this.application.paneController.setPaneLayout(PaneLayout.ItemSelection)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export enum PaneLayout {
|
||||
TagSelection = 'tag-selection',
|
||||
ItemSelection = 'item-selection',
|
||||
FilesView = 'files-view',
|
||||
TableView = 'files-view',
|
||||
Editing = 'editing',
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user