Compare commits

...
Author SHA1 Message Date
StandardNotes CI 89b77660f5 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-06-20 17:07:35 +00:00
StandardNotes CI 0699b5544e Merge branch 'develop' of https://github.com/standardnotes/app 2022-06-20 17:05:18 +00:00
Mo 31556891ef chore(workflows): git 2022-06-20 12:04:32 -05:00
Mo f527d5f265 chore(workflow): draft false if prod desktop release 2022-06-20 11:52:24 -05:00
Mo 08d07ad87a chore(workflow): promote develop workflow 2022-06-20 11:45:13 -05:00
StandardNotes CI 330f918a9d chore(release): publish
- @standardnotes/[email protected].3
2022-06-20 16:21:12 +00:00
Mo 296873d671 fix(desktop): linux executableName 2022-06-20 11:19:27 -05:00
StandardNotes CI c5804bec22 chore(release): publish
- @standardnotes/[email protected]
2022-06-20 16:09:21 +00:00
Mo 56bd64c7e6 chore: publish workflow 2022-06-20 11:07:32 -05:00
Mo 5ed29de542 chore: publish workflow 2022-06-20 11:04:16 -05:00
StandardNotes CI 4f66c96ac3 chore(release): publish
- @standardnotes/[email protected]
2022-06-20 15:59:20 +00:00
Mo 695a55f652 chore: release package dist 2022-06-20 10:57:34 -05:00
Mo 4fea3e9a6b feat: public releases package 2022-06-20 10:53:42 -05:00
Mo 3642fc882a chore(workflow): publish releases for mobile 2022-06-20 10:08:22 -05:00
StandardNotes CI d2d688b3fb chore(release): publish
- @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0-alpha.1
2022-06-20 12:21:45 +00:00
Aman Harwara affe7247ba refactor: history modal (#1122) 2022-06-20 17:50:00 +05:30
StandardNotes CI 71463a9821 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0-alpha.0
2022-06-19 11:40:45 +00:00
Aman Harwara dcf3724e2c feat: ctrl+a to select all items (#1123) 2022-06-19 17:09:03 +05:30
StandardNotes CI 3e2e03f1de chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7-alpha.0
2022-06-18 21:02:48 +00:00
Aman Harwara dfbd72b1df fix: exporting multiple notes with same title (#1119) 2022-06-19 02:30:49 +05:30
Mo e7080dfe3f chore(workflow): pr paths 2022-06-18 15:06:36 -05:00
Mo d6b8909a14 chore(workflow): pr paths 2022-06-18 15:02:34 -05:00
Mo 81263015a1 chore(workflow): pr paths 2022-06-18 14:58:04 -05:00
StandardNotes CI e369b067b0 chore(release): publish
- @standardnotes/[email protected].8
2022-06-18 15:49:53 +00:00
Mo 006e29da31 Merge branch 'release/@standardnotes/[email protected]' 2022-06-18 10:47:39 -05:00
StandardNotes CI a589a81ecf chore(release): publish
- @standardnotes/[email protected].8-alpha.0
2022-06-18 15:45:59 +00:00
Mo ea37bbe53f fix(mobile): white screen issue with switching notes on iPad 2022-06-18 10:43:41 -05:00
105 changed files with 1411 additions and 911 deletions
+6 -5
View File
@@ -20,7 +20,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- run: yarn install
@@ -54,7 +54,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- name: Set up Ruby
@@ -127,7 +127,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- run: yarn install
@@ -155,7 +155,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
@@ -178,12 +178,13 @@ jobs:
token: ${{ secrets.CI_PAT_TOKEN }}
tag_name: "@standardnotes/desktop@${{ steps.package-version.outputs.current-version}}"
prerelease: true
draft: true
draft: ${{ inputs.channel == 'beta' && true || false }}
name: "Desktop ${{ inputs.channel == 'beta' && 'Beta' || '' }} ${{ steps.package-version.outputs.current-version }}"
files: packages/desktop/dist/*
- name: Publish Snap
continue-on-error: true
run: |
sudo snap install snapcraft --classic
echo "${{ secrets.SNAPCRAFT_LOGIN_FILE }}" >> snapauth.txt
snapcraft login --with=snapauth.txt
snapcraft upload dist/standard-notes-${{ steps.package-version.outputs.current-version}}-linux-amd64.snap
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- run: yarn install
@@ -42,7 +42,7 @@ jobs:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- uses: actions/download-artifact@v3
+15 -4
View File
@@ -43,11 +43,22 @@ jobs:
with:
lane: 'android dev'
subdirectory: 'packages/mobile'
- name: Upload universal apk to artifacts
uses: actions/upload-artifact@v2
- name: get-npm-version
id: package-version
uses: martinbeentjes/npm-get-version-action@main
with:
name: dev.apk
path: android/app/build/outputs/apk/dev/release/app-dev-release.apk
path: packages/mobile
- name: Release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.CI_PAT_TOKEN }}
tag_name: "@standardnotes/mobile@${{ steps.package-version.outputs.current-version}}"
prerelease: true
draft: false
name: "Mobile Beta ${{ steps.package-version.outputs.current-version }}"
files: |
packages/mobile/android/app/build/outputs/apk/dev/release/app-dev-release.apk
ios:
defaults:
run:
+16 -8
View File
@@ -44,16 +44,24 @@ jobs:
with:
lane: 'android prod'
subdirectory: 'packages/mobile'
- name: Upload universal apk to artifacts
uses: actions/upload-artifact@v2
- name: get-npm-version
id: package-version
uses: martinbeentjes/npm-get-version-action@main
with:
name: prod.apk
path: android/app/build/outputs/apk/prod/release/app-prod-release.apk
- name: Upload Android App Bundle to artifacts
uses: actions/upload-artifact@v2
path: packages/mobile
- name: Release
uses: softprops/action-gh-release@v1
with:
name: release.aab
path: android/app/build/outputs/bundle/prodRelease/app-prod-release.aab
token: ${{ secrets.CI_PAT_TOKEN }}
tag_name: "@standardnotes/mobile@${{ steps.package-version.outputs.current-version}}"
prerelease: false
draft: false
name: "Mobile ${{ steps.package-version.outputs.current-version }}"
files: |
packages/mobile/android/app/build/outputs/bundle/prodRelease/app-prod-release.aab
packages/mobile/android/app/build/outputs/apk/prod/release/app-prod-release.apk
ios:
defaults:
run:
+2 -1
View File
@@ -6,7 +6,8 @@ on:
- develop
- main
paths:
- packages/!(components)/**
- '**/**'
- '!packages/components/**'
jobs:
test:
+37
View File
@@ -0,0 +1,37 @@
name: Promote Develop To Main
on:
workflow_dispatch:
jobs:
Run:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: develop
token: ${{ secrets.CI_PAT_TOKEN }}
fetch-depth: 0
- name: Setup git config
run: |
git config --global user.name "standardci"
git config --global user.email "[email protected]"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v4
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Merge Develop into Main
run: |
git config pull.rebase false
git checkout main
git pull origin develop
git push origin main
@@ -8,6 +8,8 @@ jobs:
Build:
if: contains(github.event.head_commit.message, 'chore(release)') == false
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@v2
@@ -15,6 +17,10 @@ jobs:
token: ${{ secrets.CI_PAT_TOKEN }}
fetch-depth: 0
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
- name: Setup git config
run: |
git config --global user.name "standardci"
@@ -36,7 +42,9 @@ jobs:
continue-on-error: true
id: graduateRelease
if: ${{ github.ref == 'refs/heads/main' }}
run: yarn release:prod:graduate
run: |
yarn release:prod:graduate
yarn publish:prod
- name: Bump Prod Version Fallback
if: ${{ always() && github.ref == 'refs/heads/main' && steps.graduateRelease.outcome == 'failure' }}
@@ -44,7 +52,18 @@ jobs:
echo Falling back to non-graduate release due to https://github.com/lerna/lerna/issues/2532
git stash
yarn release:prod
yarn publish:prod
- name: Bump Beta Version
if: ${{ github.ref == 'refs/heads/develop' }}
run: yarn release:beta
run: |
yarn release:beta
yarn publish:beta
- name: Merge release into develop
if: ${{ github.ref == 'refs/heads/main' }}
run: |
git config pull.rebase false
git checkout develop
git pull origin main
git push origin develop
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -28,6 +28,8 @@
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
"release:prod:graduate": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish\"",
"release:beta": "lerna version --conventional-prerelease --conventional-commits --yes -m \"chore(release): publish\"",
"publish:prod": "lerna publish from-git --yes",
"publish:beta": "lerna publish from-git --yes --dist-tag alpha",
"version": "yarn install --no-immutable && git add yarn.lock",
"postversion": "./scripts/push-tags-one-by-one.sh",
"lerna:list": " yarn lerna list -all",
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.22.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.22.12-alpha.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
### Bug Fixes
* **desktop:** linux executableName ([296873d](https://github.com/standardnotes/app/commit/296873d671d6cc7cb8744747c09bf755e3a6f1e0))
## [3.22.12-alpha.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.22.12-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.22.12-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.22.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/desktop
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.22.11",
"version": "3.22.12",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
@@ -123,7 +123,7 @@
"linux": {
"category": "Office",
"icon": "build/icon/",
"executableName": "Standard Notes",
"executableName": "standard-notes",
"desktop": {
"StartupWMClass": "standard notes"
},
+10
View File
@@ -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.
## [3.23.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.23.8-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
### Bug Fixes
* **mobile:** white screen issue with switching notes on iPad ([ea37bbe](https://github.com/standardnotes/app/commit/ea37bbe53fb46269bd1d13bd5cf85d328690c232))
## [3.23.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.23.7",
"version": "3.23.8",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+2
View File
@@ -53,6 +53,8 @@ export const Root = () => {
const removeNoteObserver = application?.editorGroup.addActiveControllerChangeObserver(activeController => {
if (activeController instanceof NoteViewController) {
setActiveNoteView(activeController)
} else {
setActiveNoteView(undefined)
}
})
+1
View File
@@ -0,0 +1 @@
dist
+18
View File
@@ -0,0 +1,18 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.1.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/releases
# [1.1.0-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/releases
# 1.1.0-alpha.0 (2022-06-20)
### Features
* public releases package ([4fea3e9](https://github.com/standardnotes/app/commit/4fea3e9a6b5fdeea772ca903fccff7034b6e2928))
+15
View File
@@ -0,0 +1,15 @@
import Components from '../components/package.json' assert { type: 'json' }
import Desktop from '../desktop/package.json' assert { type: 'json' }
import Mobile from '../mobile/package.json' assert { type: 'json' }
import Web from '../web/package.json' assert { type: 'json' }
import { writeJson, ensureDirExists } from '../../scripts/ScriptUtils.mjs'
const Releases = {
[Components.name]: Components.version,
[Desktop.name]: Desktop.version,
[Mobile.name]: Mobile.version,
[Web.name]: Web.version,
}
ensureDirExists('dist')
writeJson(Releases, 'dist/releases.json')
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@standardnotes/releases",
"version": "1.1.0",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"publishConfig": {
"access": "public"
},
"author": "Standard Notes.",
"description": "Standard Notes client release versions",
"engines": {
"node": ">=12.19.0 <17.0.0"
},
"scripts": {
"build": "node index.mjs",
"version": "yarn build"
},
"devDependencies": {
"@types/node": "*",
"typescript": "*"
}
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/web-server
## [1.2.14-alpha.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/web-server
## [1.2.14-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-19)
**Note:** Version bump only for package @standardnotes/web-server
## [1.2.14-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/web-server
## [1.2.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/web-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web-server",
"version": "1.2.13",
"version": "1.2.14",
"license": "AGPL-3.0-or-later",
"private": true,
"author": "Standard Notes.",
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.23.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/web
# [3.23.0-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-20)
**Note:** Version bump only for package @standardnotes/web
# [3.23.0-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-19)
### Features
* ctrl+a to select all items ([#1123](https://github.com/standardnotes/app/issues/1123)) ([dcf3724](https://github.com/standardnotes/app/commit/dcf3724e2c951d7bbad276d37b3302c34d7ee78f))
## [3.22.7-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
### Bug Fixes
* exporting multiple notes with same title ([#1119](https://github.com/standardnotes/app/issues/1119)) ([dfbd72b](https://github.com/standardnotes/app/commit/dfbd72b1dfd4a4681c452c9113248045e69880cd))
## [3.22.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-06-18)
**Note:** Version bump only for package @standardnotes/web
+2 -2
View File
@@ -1,9 +1,9 @@
{
"name": "@standardnotes/web",
"version": "3.22.6",
"version": "3.23.0",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes",
"author": "Standard Notes.",
"private": true,
"files": [
"dist"
@@ -14,7 +14,7 @@ import ChallengeModal from '@/Components/ChallengeModal/ChallengeModal'
import NotesContextMenu from '@/Components/NotesContextMenu/NotesContextMenu'
import PurchaseFlowWrapper from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import RevisionHistoryModalWrapper from '@/Components/RevisionHistoryModal/RevisionHistoryModalWrapper'
import RevisionHistoryModal from '@/Components/RevisionHistoryModal/RevisionHistoryModal'
import PremiumModalProvider from '@/Hooks/usePremiumModal'
import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
import TagsContextMenuWrapper from '@/Components/Tags/TagContextMenu'
@@ -177,7 +177,18 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
<div className={platformString + ' main-ui-view sn-component'}>
<div id="app" className={appClass + ' app app-column-container'}>
<Navigation application={application} />
<ContentListView application={application} viewControllerManager={viewControllerManager} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />
</div>
@@ -185,7 +196,13 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModalWrapper application={application} viewControllerManager={viewControllerManager} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>
{renderChallenges()}
@@ -196,6 +213,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagsContextMenuWrapper viewControllerManager={viewControllerManager} />
<FileContextMenuWrapper
@@ -15,12 +15,10 @@ import UrlMissing from '@/Components/ComponentView/UrlMissing'
import IsDeprecated from '@/Components/ComponentView/IsDeprecated'
import IsExpired from '@/Components/ComponentView/IsExpired'
import IssueOnLoading from '@/Components/ComponentView/IssueOnLoading'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
interface IProps {
application: WebApplication
viewControllerManager: ViewControllerManager
componentViewer: ComponentViewer
requestReload?: (viewer: ComponentViewer, force?: boolean) => void
onLoad?: (component: SNComponent) => void
@@ -1,32 +1,44 @@
import { WebApplication } from '@/Application/Application'
import { KeyboardKey } from '@/Services/IOService'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { UuidString } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants/Constants'
import { ListableContentItem } from './Types/ListableContentItem'
import ContentListItem from './ContentListItem'
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { ElementIds } from '@/Constants/ElementIDs'
type Props = {
application: WebApplication
viewControllerManager: ViewControllerManager
filesController: FilesController
itemListController: ItemListController
items: ListableContentItem[]
navigationController: NavigationController
notesController: NotesController
selectionController: SelectedItemsController
selectedItems: Record<UuidString, ListableContentItem>
paginate: () => void
}
const ContentList: FunctionComponent<Props> = ({
application,
viewControllerManager,
filesController,
itemListController,
items,
navigationController,
notesController,
selectionController,
selectedItems,
paginate,
}) => {
const { selectPreviousItem, selectNextItem } = viewControllerManager.itemListController
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } =
viewControllerManager.itemListController.webDisplayOptions
const { sortBy } = viewControllerManager.itemListController.displayOptions
const { selectPreviousItem, selectNextItem } = itemListController
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions
const { sortBy } = itemListController.displayOptions
const onScroll: UIEventHandler = useCallback(
(e) => {
@@ -55,7 +67,7 @@ const ContentList: FunctionComponent<Props> = ({
return (
<div
className="infinite-scroll focus:shadow-none focus:outline-none"
id="notes-scrollable"
id={ElementIds.ContentList}
onScroll={onScroll}
onKeyDown={onKeyDown}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
@@ -71,10 +83,10 @@ const ContentList: FunctionComponent<Props> = ({
hideTags={hideTags}
hideIcon={hideEditorIcon}
sortBy={sortBy}
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
filesController={filesController}
selectionController={selectionController}
navigationController={navigationController}
notesController={notesController}
/>
))}
</div>
@@ -7,22 +7,22 @@ import Menu from '@/Components/Menu/Menu'
import MenuItem from '@/Components/Menu/MenuItem'
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
type Props = {
application: WebApplication
viewControllerManager: ViewControllerManager
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
closeDisplayOptionsMenu: () => void
isOpen: boolean
navigationController: NavigationController
}
const ContentListOptionsMenu: FunctionComponent<Props> = ({
closeDisplayOptionsMenu,
closeOnBlur,
application,
viewControllerManager,
isOpen,
navigationController,
}) => {
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
@@ -174,7 +174,7 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
</MenuItem>
<MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
{viewControllerManager.navigationController.selectedUuid !== SystemViewId.Files && (
{navigationController.selectedUuid !== SystemViewId.Files && (
<MenuItem
type={MenuItemType.SwitchButton}
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
@@ -1,6 +1,5 @@
import { KeyboardKey, KeyboardModifier } from '@/Services/IOService'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
@@ -15,20 +14,49 @@ import {
useState,
} from 'react'
import ContentList from '@/Components/ContentListView/ContentList'
import NoAccountWarningWrapper from '@/Components/NoAccountWarning/NoAccountWarning'
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import ContentListOptionsMenu from './ContentListOptionsMenu'
import Icon from '@/Components/Icon/Icon'
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FilesController } from '@/Controllers/FilesController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
import { NotesController } from '@/Controllers/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { ElementIds } from '@/Constants/ElementIDs'
type Props = {
accountMenuController: AccountMenuController
application: WebApplication
viewControllerManager: ViewControllerManager
filesController: FilesController
itemListController: ItemListController
navigationController: NavigationController
noAccountWarningController: NoAccountWarningController
noteTagsController: NoteTagsController
notesController: NotesController
searchOptionsController: SearchOptionsController
selectionController: SelectedItemsController
}
const ContentListView: FunctionComponent<Props> = ({ application, viewControllerManager }) => {
const ContentListView: FunctionComponent<Props> = ({
accountMenuController,
application,
filesController,
itemListController,
navigationController,
noAccountWarningController,
noteTagsController,
notesController,
searchOptionsController,
selectionController,
}) => {
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
@@ -47,9 +75,9 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
paginate,
panelWidth,
createNewNote,
} = viewControllerManager.itemListController
} = itemListController
const { selectedItems } = viewControllerManager.selectionController
const { selectedItems } = selectionController
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
const [focusedSearch, setFocusedSearch] = useState(false)
@@ -57,17 +85,17 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
const isFilesSmartView = useMemo(
() => viewControllerManager.navigationController.selected?.uuid === SystemViewId.Files,
[viewControllerManager.navigationController.selected?.uuid],
() => navigationController.selected?.uuid === SystemViewId.Files,
[navigationController.selected?.uuid],
)
const addNewItem = useCallback(() => {
if (isFilesSmartView) {
void viewControllerManager.filesController.uploadNewFile()
void filesController.uploadNewFile()
} else {
void createNewNote()
}
}, [viewControllerManager.filesController, createNewNote, isFilesSmartView])
}, [filesController, createNewNote, isFilesSmartView])
useEffect(() => {
/**
@@ -75,7 +103,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
const newNoteKeyObserver = application.io.addKeyObserver({
const disposeNewNoteKeyObserver = application.io.addKeyObserver({
key: 'n',
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl],
onKeyDown: (event) => {
@@ -84,7 +112,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
},
})
const nextNoteKeyObserver = application.io.addKeyObserver({
const disposeNextNoteKeyObserver = application.io.addKeyObserver({
key: KeyboardKey.Down,
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
onKeyDown: () => {
@@ -95,7 +123,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
},
})
const previousNoteKeyObserver = application.io.addKeyObserver({
const disposePreviousNoteKeyObserver = application.io.addKeyObserver({
key: KeyboardKey.Up,
element: document.body,
onKeyDown: () => {
@@ -103,7 +131,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
},
})
const searchKeyObserver = application.io.addKeyObserver({
const disposeSearchKeyObserver = application.io.addKeyObserver({
key: 'f',
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift],
onKeyDown: () => {
@@ -113,13 +141,37 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
},
})
const disposeSelectAllKeyObserver = application.io.addKeyObserver({
key: 'a',
modifiers: [KeyboardModifier.Ctrl],
onKeyDown: (event) => {
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
if (!isTargetInsideContentList) {
return
}
event.preventDefault()
selectionController.selectAll()
},
})
return () => {
newNoteKeyObserver()
nextNoteKeyObserver()
previousNoteKeyObserver()
searchKeyObserver()
disposeNewNoteKeyObserver()
disposeNextNoteKeyObserver()
disposePreviousNoteKeyObserver()
disposeSearchKeyObserver()
disposeSelectAllKeyObserver()
}
}, [addNewItem, application.io, createNewNote, searchBarElement, selectNextItem, selectPreviousItem])
}, [
addNewItem,
application.io,
createNewNote,
searchBarElement,
selectNextItem,
selectPreviousItem,
selectionController,
])
const onNoteFilterTextChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
@@ -143,15 +195,15 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
noteTagsController.reloadTagsContainerMaxWidth()
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
},
[viewControllerManager, application],
[application, noteTagsController],
)
const panelWidthEventCallback = useCallback(() => {
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
}, [viewControllerManager])
noteTagsController.reloadTagsContainerMaxWidth()
}, [noteTagsController])
const toggleDisplayOptionsMenu = useCallback(() => {
setShowDisplayOptionsMenu(!showDisplayOptionsMenu)
@@ -207,11 +259,14 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
{(focusedSearch || noteFilterText) && (
<div className="animate-fade-from-top">
<SearchOptions application={application} viewControllerManager={viewControllerManager} />
<SearchOptions application={application} searchOptions={searchOptionsController} />
</div>
)}
</div>
<NoAccountWarningWrapper viewControllerManager={viewControllerManager} />
<NoAccountWarning
accountMenuController={accountMenuController}
noAccountWarningController={noAccountWarningController}
/>
</div>
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
<div className="sk-app-bar no-edges">
@@ -234,10 +289,10 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
{showDisplayOptionsMenu && (
<ContentListOptionsMenu
application={application}
viewControllerManager={viewControllerManager}
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
closeOnBlur={closeDisplayOptMenuOnBlur}
isOpen={showDisplayOptionsMenu}
navigationController={navigationController}
/>
)}
</DisclosurePanel>
@@ -253,8 +308,12 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
items={renderedItems}
selectedItems={selectedItems}
application={application}
viewControllerManager={viewControllerManager}
paginate={paginate}
filesController={filesController}
itemListController={itemListController}
navigationController={navigationController}
notesController={notesController}
selectionController={selectionController}
/>
) : null}
</div>
@@ -13,6 +13,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl
import { NotesController } from '@/Controllers/NotesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
type Props = {
application: WebApplication
@@ -23,6 +24,7 @@ type Props = {
notesController: NotesController
noteTagsController: NoteTagsController
selectionController: SelectedItemsController
historyModalController: HistoryModalController
}
const MultipleSelectedNotes = ({
@@ -34,6 +36,7 @@ const MultipleSelectedNotes = ({
notesController,
noteTagsController,
selectionController,
historyModalController,
}: Props) => {
const count = notesController.selectedNotesCount
@@ -65,6 +68,7 @@ const MultipleSelectedNotes = ({
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
historyModalController={historyModalController}
/>
</div>
</div>
@@ -1,49 +1,22 @@
import Icon from '@/Components/Icon/Icon'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
import { observer } from 'mobx-react-lite'
import { MouseEventHandler, useCallback } from 'react'
import NoAccountWarningContent from './NoAccountWarningContent'
type Props = { viewControllerManager: ViewControllerManager }
const NoAccountWarning = observer(({ viewControllerManager }: Props) => {
const showAccountMenu: MouseEventHandler = useCallback(
(event) => {
event.stopPropagation()
viewControllerManager.accountMenuController.setShow(true)
},
[viewControllerManager],
)
const hideWarning = useCallback(() => {
viewControllerManager.noAccountWarningController.hide()
}, [viewControllerManager])
return (
<div className="mt-4 p-4 rounded-md shadow-sm grid grid-template-cols-1fr">
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
<p className="m-0 mt-1 col-start-1 col-end-3">Sign in or register to back up your notes.</p>
<button className="sn-button small info mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
Open Account menu
</button>
<button
onClick={hideWarning}
title="Ignore warning"
aria-label="Ignore warning"
style={{ height: '20px' }}
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
>
<Icon type="close" className="block" />
</button>
</div>
)
})
NoAccountWarning.displayName = 'NoAccountWarning'
const NoAccountWarningWrapper = ({ viewControllerManager }: Props) => {
const canShow = viewControllerManager.noAccountWarningController.show
return canShow ? <NoAccountWarning viewControllerManager={viewControllerManager} /> : null
type Props = {
accountMenuController: AccountMenuController
noAccountWarningController: NoAccountWarningController
}
export default observer(NoAccountWarningWrapper)
const NoAccountWarning = ({ accountMenuController, noAccountWarningController }: Props) => {
const canShow = noAccountWarningController.show
return canShow ? (
<NoAccountWarningContent
accountMenuController={accountMenuController}
noAccountWarningController={noAccountWarningController}
/>
) : null
}
export default observer(NoAccountWarning)
@@ -0,0 +1,45 @@
import Icon from '@/Components/Icon/Icon'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
import { observer } from 'mobx-react-lite'
import { MouseEventHandler, useCallback } from 'react'
type Props = {
accountMenuController: AccountMenuController
noAccountWarningController: NoAccountWarningController
}
const NoAccountWarningContent = ({ accountMenuController, noAccountWarningController }: Props) => {
const showAccountMenu: MouseEventHandler = useCallback(
(event) => {
event.stopPropagation()
accountMenuController.setShow(true)
},
[accountMenuController],
)
const hideWarning = useCallback(() => {
noAccountWarningController.hide()
}, [noAccountWarningController])
return (
<div className="mt-4 p-4 rounded-md shadow-sm grid grid-template-cols-1fr">
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
<p className="m-0 mt-1 col-start-1 col-end-3">Sign in or register to back up your notes.</p>
<button className="sn-button small info mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
Open Account menu
</button>
<button
onClick={hideWarning}
title="Ignore warning"
aria-label="Ignore warning"
style={{ height: '20px' }}
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
>
<Icon type="close" className="block" />
</button>
</div>
)
}
export default observer(NoAccountWarningContent)
@@ -92,6 +92,7 @@ class NoteGroupView extends PureComponent<Props, State> {
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
/>
)}
@@ -971,6 +971,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
@@ -1002,7 +1003,6 @@ class NoteView extends PureComponent<NoteViewProps, State> {
onLoad={this.onEditorComponentLoad}
requestReload={this.editorComponentViewerRequestsReload}
application={this.application}
viewControllerManager={this.viewControllerManager}
/>
</div>
)}
@@ -1073,12 +1073,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
{this.state.stackComponentViewers.map((viewer) => {
return (
<div className="component-view component-stack-item" key={viewer.identifier}>
<ComponentView
key={viewer.identifier}
componentViewer={viewer}
application={this.application}
viewControllerManager={this.viewControllerManager}
/>
<ComponentView key={viewer.identifier} componentViewer={viewer} application={this.application} />
</div>
)
})}
@@ -7,15 +7,23 @@ import { WebApplication } from '@/Application/Application'
import { NotesController } from '@/Controllers/NotesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
type Props = {
application: WebApplication
navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
historyModalController: HistoryModalController
}
const NotesContextMenu = ({ application, navigationController, notesController, noteTagsController }: Props) => {
const NotesContextMenu = ({
application,
navigationController,
notesController,
noteTagsController,
historyModalController,
}: Props) => {
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = notesController
const contextMenuRef = useRef<HTMLDivElement>(null)
@@ -49,6 +57,7 @@ const NotesContextMenu = ({ application, navigationController, notesController,
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
historyModalController={historyModalController}
/>
</div>
) : null
@@ -174,6 +174,7 @@ const NotesOptions = ({
navigationController,
notesController,
noteTagsController,
historyModalController,
closeOnBlur,
}: NotesOptionsProps) => {
const [altKeyDown, setAltKeyDown] = useState(false)
@@ -222,7 +223,7 @@ const NotesOptions = ({
const format = editor?.package_info?.file_type || 'txt'
return `${note.title}.${format}`
},
[application],
[application.componentManager],
)
const downloadSelectedItems = useCallback(async () => {
@@ -239,7 +240,7 @@ const NotesOptions = ({
await application.getArchiveService().downloadDataAsZip(
notes.map((note) => {
return {
filename: getNoteFileName(note),
name: getNoteFileName(note),
content: new Blob([note.text]),
}
}),
@@ -259,8 +260,8 @@ const NotesOptions = ({
}, [application, notes])
const openRevisionHistoryModal = useCallback(() => {
notesController.setShowRevisionHistoryModal(true)
}, [notesController])
historyModalController.openModal(notesController.firstSelectedNote)
}, [historyModalController, notesController.firstSelectedNote])
return (
<>
@@ -10,12 +10,14 @@ import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { NotesController } from '@/Controllers/NotesController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
type Props = {
application: WebApplication
navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
historyModalController: HistoryModalController
onClickPreprocessing?: () => Promise<void>
}
@@ -24,6 +26,7 @@ const NotesOptionsPanel = ({
navigationController,
notesController,
noteTagsController,
historyModalController,
onClickPreprocessing,
}: Props) => {
const [open, setOpen] = useState(false)
@@ -95,6 +98,7 @@ const NotesOptionsPanel = ({
navigationController={navigationController}
notesController={notesController}
noteTagsController={noteTagsController}
historyModalController={historyModalController}
closeOnBlur={closeOnBlur}
/>
)}
@@ -1,4 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { NoteTagsController } from '@/Controllers/NoteTagsController'
@@ -8,5 +9,6 @@ export type NotesOptionsProps = {
navigationController: NavigationController
notesController: NotesController
noteTagsController: NoteTagsController
historyModalController: HistoryModalController
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
}
@@ -1,75 +1,24 @@
import { WebApplication } from '@/Application/Application'
import { Action, ActionVerb, HistoryEntry, NoteHistoryEntry, RevisionListEntry, SNNote } from '@standardnotes/snjs'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
import { FeaturesClientInterface } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useState, useEffect, SetStateAction, Dispatch } from 'react'
import { FunctionComponent } from 'react'
import LegacyHistoryList from './LegacyHistoryList'
import RemoteHistoryList from './RemoteHistoryList'
import { RevisionType } from './RevisionType'
import SessionHistoryList from './SessionHistoryList'
import { LegacyHistoryEntry, RemoteRevisionListGroup, sortRevisionListIntoGroups } from './utils'
export enum RevisionListTabType {
Session = 'Session',
Remote = 'Remote',
Legacy = 'Legacy',
}
type Props = {
application: WebApplication
isFetchingRemoteHistory: boolean
note: SNNote
remoteHistory: RemoteRevisionListGroup[] | undefined
setIsFetchingSelectedRevision: Dispatch<SetStateAction<boolean>>
setSelectedRemoteEntry: Dispatch<SetStateAction<RevisionListEntry | undefined>>
setSelectedRevision: Dispatch<SetStateAction<HistoryEntry | LegacyHistoryEntry | undefined>>
setShowContentLockedScreen: Dispatch<SetStateAction<boolean>>
features: FeaturesClientInterface
noteHistoryController: NoteHistoryController
}
const HistoryListContainer: FunctionComponent<Props> = ({
application,
isFetchingRemoteHistory,
note,
remoteHistory,
setIsFetchingSelectedRevision,
setSelectedRemoteEntry,
setSelectedRevision,
setShowContentLockedScreen,
}) => {
const sessionHistory = sortRevisionListIntoGroups<NoteHistoryEntry>(
application.historyManager.sessionHistoryForItem(note) as NoteHistoryEntry[],
)
const [legacyHistory, setLegacyHistory] = useState<Action[]>()
const [selectedTab, setSelectedTab] = useState<RevisionListTabType>(RevisionListTabType.Remote)
useEffect(() => {
const fetchLegacyHistory = async () => {
const actionExtensions = application.actionsManager.getExtensions()
actionExtensions.forEach(async (ext) => {
const actionExtension = await application.actionsManager.loadExtensionInContextOfItem(ext, note)
if (!actionExtension) {
return
}
const isLegacyNoteHistoryExt = actionExtension?.actions.some((action) => action.verb === ActionVerb.Nested)
if (!isLegacyNoteHistoryExt) {
return
}
const legacyHistoryEntries = actionExtension.actions.filter((action) => action.subactions?.[0])
setLegacyHistory(legacyHistoryEntries)
})
}
fetchLegacyHistory().catch(console.error)
}, [application, note])
const HistoryListContainer: FunctionComponent<Props> = ({ features, noteHistoryController }) => {
const { legacyHistory, currentTab, selectTab } = noteHistoryController
const TabButton: FunctionComponent<{
type: RevisionListTabType
type: RevisionType
}> = ({ type }) => {
const isSelected = selectedTab === type
const isSelected = currentTab === type
return (
<button
@@ -77,8 +26,7 @@ const HistoryListContainer: FunctionComponent<Props> = ({
isSelected ? 'color-info font-medium shadow-bottom' : 'color-text'
}`}
onClick={() => {
setSelectedTab(type)
setSelectedRemoteEntry(undefined)
selectTab(type)
}}
>
{type}
@@ -86,98 +34,26 @@ const HistoryListContainer: FunctionComponent<Props> = ({
)
}
const fetchAndSetLegacyRevision = useCallback(
async (revisionListEntry: Action) => {
setSelectedRemoteEntry(undefined)
setSelectedRevision(undefined)
setIsFetchingSelectedRevision(true)
try {
if (!revisionListEntry.subactions?.[0]) {
throw new Error('Could not find revision action url')
}
const response = await application.actionsManager.runAction(revisionListEntry.subactions[0], note)
if (!response) {
throw new Error('Could not fetch revision')
}
setSelectedRevision(response.item as unknown as HistoryEntry)
} catch (error) {
console.error(error)
setSelectedRevision(undefined)
} finally {
setIsFetchingSelectedRevision(false)
}
},
[application.actionsManager, note, setIsFetchingSelectedRevision, setSelectedRemoteEntry, setSelectedRevision],
)
const fetchAndSetRemoteRevision = useCallback(
async (revisionListEntry: RevisionListEntry) => {
setShowContentLockedScreen(false)
if (application.features.hasMinimumRole(revisionListEntry.required_role)) {
setIsFetchingSelectedRevision(true)
setSelectedRevision(undefined)
setSelectedRemoteEntry(undefined)
try {
const remoteRevision = await application.historyManager.fetchRemoteRevision(note, revisionListEntry)
setSelectedRevision(remoteRevision)
setSelectedRemoteEntry(revisionListEntry)
} catch (err) {
console.error(err)
} finally {
setIsFetchingSelectedRevision(false)
}
} else {
setShowContentLockedScreen(true)
setSelectedRevision(undefined)
}
},
[
application,
note,
setIsFetchingSelectedRevision,
setSelectedRemoteEntry,
setSelectedRevision,
setShowContentLockedScreen,
],
)
const CurrentTabList = () => {
switch (currentTab) {
case RevisionType.Remote:
return <RemoteHistoryList features={features} noteHistoryController={noteHistoryController} />
case RevisionType.Session:
return <SessionHistoryList noteHistoryController={noteHistoryController} />
case RevisionType.Legacy:
return <LegacyHistoryList legacyHistory={legacyHistory} noteHistoryController={noteHistoryController} />
}
}
return (
<div className={'flex flex-col min-w-60 border-0 border-r-1px border-solid border-main overflow-auto h-full'}>
<div className="flex border-0 border-b-1 border-solid border-main">
<TabButton type={RevisionListTabType.Remote} />
<TabButton type={RevisionListTabType.Session} />
{legacyHistory && legacyHistory.length > 0 && <TabButton type={RevisionListTabType.Legacy} />}
<TabButton type={RevisionType.Remote} />
<TabButton type={RevisionType.Session} />
{legacyHistory && legacyHistory.length > 0 && <TabButton type={RevisionType.Legacy} />}
</div>
<div className={'min-h-0 overflow-auto py-1.5 h-full'}>
{selectedTab === RevisionListTabType.Session && (
<SessionHistoryList
sessionHistory={sessionHistory}
setSelectedRevision={setSelectedRevision}
setSelectedRemoteEntry={setSelectedRemoteEntry}
/>
)}
{selectedTab === RevisionListTabType.Remote && (
<RemoteHistoryList
application={application}
remoteHistory={remoteHistory}
isFetchingRemoteHistory={isFetchingRemoteHistory}
fetchAndSetRemoteRevision={fetchAndSetRemoteRevision}
/>
)}
{selectedTab === RevisionListTabType.Legacy && (
<LegacyHistoryList
legacyHistory={legacyHistory}
setSelectedRevision={setSelectedRevision}
setSelectedRemoteEntry={setSelectedRemoteEntry}
fetchAndSetLegacyRevision={fetchAndSetLegacyRevision}
/>
)}
<CurrentTabList />
</div>
</div>
)
@@ -0,0 +1,50 @@
import RevisionContentLocked from './RevisionContentLocked'
import SelectedRevisionContent from './SelectedRevisionContent'
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/Application/Application'
import { NotesController } from '@/Controllers/NotesController'
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
import { NoteHistoryController, RevisionContentState } from '@/Controllers/NoteHistory/NoteHistoryController'
type Props = {
application: WebApplication
noteHistoryController: NoteHistoryController
notesController: NotesController
subscriptionController: SubscriptionController
}
const HistoryModalContentPane = ({
application,
noteHistoryController,
notesController,
subscriptionController,
}: Props) => {
const { contentState } = noteHistoryController
switch (contentState) {
case RevisionContentState.Idle:
return (
<div className="color-passive-0 select-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
No revision selected
</div>
)
case RevisionContentState.Loading:
return (
<div className="sk-spinner w-5 h-5 spinner-info absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
)
case RevisionContentState.Loaded:
return (
<SelectedRevisionContent
application={application}
notesController={notesController}
noteHistoryController={noteHistoryController}
/>
)
case RevisionContentState.NotEntitled:
return <RevisionContentLocked subscriptionController={subscriptionController} />
default:
return null
}
}
export default observer(HistoryModalContentPane)
@@ -0,0 +1,33 @@
import { getPlatformString } from '@/Utils'
import { DialogOverlay, DialogContent } from '@reach/dialog'
import { ReactNode } from 'react'
type Props = {
children: ReactNode
onDismiss: () => void
}
const HistoryModalDialog = ({ children, onDismiss }: Props) => {
return (
<DialogOverlay
className={`sn-component ${getPlatformString()}`}
onDismiss={onDismiss}
aria-label="Note revision history"
>
<DialogContent
aria-label="Note revision history"
className="rounded shadow-overlay"
style={{
width: '90%',
maxWidth: '90%',
minHeight: '90%',
background: 'var(--modal-background-color)',
}}
>
<div className="bg-default flex flex-col h-full overflow-hidden">{children}</div>
</DialogContent>
</DialogOverlay>
)
}
export default HistoryModalDialog
@@ -0,0 +1,37 @@
import { observer } from 'mobx-react-lite'
import { useState } from 'react'
import HistoryListContainer from './HistoryListContainer'
import { RevisionHistoryModalContentProps } from './RevisionHistoryModalProps'
import HistoryModalFooter from './HistoryModalFooter'
import HistoryModalContentPane from './HistoryModalContentPane'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
const HistoryModalDialogContent = ({
application,
dismissModal,
notesController,
subscriptionController,
note,
selectionController,
}: RevisionHistoryModalContentProps) => {
const [noteHistoryController] = useState(() => new NoteHistoryController(application, note, selectionController))
return (
<>
<div className="flex flex-grow min-h-0">
<HistoryListContainer features={application.features} noteHistoryController={noteHistoryController} />
<div className="flex flex-col flex-grow relative">
<HistoryModalContentPane
application={application}
noteHistoryController={noteHistoryController}
notesController={notesController}
subscriptionController={subscriptionController}
/>
</div>
</div>
<HistoryModalFooter dismissModal={dismissModal} noteHistoryController={noteHistoryController} />
</>
)
}
export default observer(HistoryModalDialogContent)
@@ -0,0 +1,62 @@
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
import { RevisionListEntry } from '@standardnotes/snjs/dist/@types'
import { observer } from 'mobx-react-lite'
import { useCallback, useState } from 'react'
import Button from '../Button/Button'
type Props = {
dismissModal: () => void
noteHistoryController: NoteHistoryController
}
const HistoryModalFooter = ({ dismissModal, noteHistoryController }: Props) => {
const { selectedRevision, restoreRevision, restoreRevisionAsCopy, selectedEntry, deleteRemoteRevision } =
noteHistoryController
const [isDeletingRevision, setIsDeletingRevision] = useState(false)
const restoreSelectedRevision = useCallback(() => {
if (selectedRevision) {
restoreRevision(selectedRevision)
dismissModal()
}
}, [dismissModal, restoreRevision, selectedRevision])
const restoreAsCopy = useCallback(async () => {
if (selectedRevision) {
void restoreRevisionAsCopy(selectedRevision)
dismissModal()
}
}, [dismissModal, restoreRevisionAsCopy, selectedRevision])
const deleteSelectedRevision = useCallback(async () => {
if (!selectedEntry) {
return
}
setIsDeletingRevision(true)
await deleteRemoteRevision(selectedEntry as RevisionListEntry)
setIsDeletingRevision(false)
}, [deleteRemoteRevision, selectedEntry])
return (
<div className="flex flex-shrink-0 justify-between items-center min-h-6 px-2.5 py-2 border-0 border-t-1px border-solid border-main">
<div>
<Button className="py-1.35" label="Close" onClick={dismissModal} variant="normal" />
</div>
{selectedRevision && (
<div className="flex items-center">
{(selectedEntry as RevisionListEntry).uuid && (
<Button className="py-1.35 mr-2.5" onClick={deleteSelectedRevision} variant="normal">
{isDeletingRevision ? <div className="sk-spinner my-1 w-3 h-3 spinner-info" /> : 'Delete this revision'}
</Button>
)}
<Button className="py-1.35 mr-2.5" label="Restore as a copy" onClick={restoreAsCopy} variant="normal" />
<Button className="py-1.35" label="Restore version" onClick={restoreSelectedRevision} variant="primary" />
</div>
)}
</div>
)
}
export default observer(HistoryModalFooter)
@@ -1,48 +1,21 @@
import { Action, HistoryEntry, RevisionListEntry } from '@standardnotes/snjs'
import { Dispatch, FunctionComponent, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Action } from '@standardnotes/snjs'
import { FunctionComponent, useRef } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { LegacyHistoryEntry } from './utils'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
type Props = {
legacyHistory: Action[] | undefined
setSelectedRevision: Dispatch<SetStateAction<HistoryEntry | LegacyHistoryEntry | undefined>>
setSelectedRemoteEntry: Dispatch<SetStateAction<RevisionListEntry | undefined>>
fetchAndSetLegacyRevision: (revisionListEntry: Action) => Promise<void>
noteHistoryController: NoteHistoryController
}
const LegacyHistoryList: FunctionComponent<Props> = ({
legacyHistory,
setSelectedRevision,
setSelectedRemoteEntry,
fetchAndSetLegacyRevision,
}) => {
const LegacyHistoryList: FunctionComponent<Props> = ({ legacyHistory, noteHistoryController }) => {
const { selectLegacyRevision, selectedEntry } = noteHistoryController
const legacyHistoryListRef = useRef<HTMLDivElement>(null)
useListKeyboardNavigation(legacyHistoryListRef)
const [selectedItemUrl, setSelectedItemUrl] = useState<string>()
const firstEntry = useMemo(() => {
return legacyHistory?.[0]
}, [legacyHistory])
const selectFirstEntry = useCallback(() => {
if (firstEntry) {
setSelectedItemUrl(firstEntry.subactions?.[0].url)
setSelectedRevision(undefined)
fetchAndSetLegacyRevision(firstEntry).catch(console.error)
}
}, [fetchAndSetLegacyRevision, firstEntry, setSelectedRevision])
useEffect(() => {
if (firstEntry && !selectedItemUrl) {
selectFirstEntry()
} else if (!firstEntry) {
setSelectedRevision(undefined)
}
}, [firstEntry, selectFirstEntry, selectedItemUrl, setSelectedRevision])
return (
<div
className={`flex flex-col w-full h-full focus:shadow-none ${
@@ -51,16 +24,15 @@ const LegacyHistoryList: FunctionComponent<Props> = ({
ref={legacyHistoryListRef}
>
{legacyHistory?.map((entry) => {
const selectedEntryUrl = (selectedEntry as Action)?.subactions?.[0].url
const url = entry.subactions?.[0].url
return (
<HistoryListItem
key={url}
isSelected={selectedItemUrl === url}
isSelected={selectedEntryUrl === url}
onClick={() => {
setSelectedItemUrl(url)
setSelectedRemoteEntry(undefined)
fetchAndSetLegacyRevision(entry).catch(console.error)
selectLegacyRevision(entry)
}}
>
{entry.label}
@@ -1,50 +1,26 @@
import { WebApplication } from '@/Application/Application'
import { RevisionListEntry } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { Fragment, FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import Icon from '@/Components/Icon/Icon'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { previewHistoryEntryTitle, RemoteRevisionListGroup } from './utils'
import { previewHistoryEntryTitle } from './utils'
import { FeaturesClientInterface, RevisionListEntry } from '@standardnotes/snjs/dist/@types'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
type RemoteHistoryListProps = {
application: WebApplication
remoteHistory: RemoteRevisionListGroup[] | undefined
isFetchingRemoteHistory: boolean
fetchAndSetRemoteRevision: (revisionListEntry: RevisionListEntry) => Promise<void>
features: FeaturesClientInterface
noteHistoryController: NoteHistoryController
}
const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
application,
remoteHistory,
isFetchingRemoteHistory,
fetchAndSetRemoteRevision,
}) => {
const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({ features, noteHistoryController }) => {
const { remoteHistory, isFetchingRemoteHistory, selectRemoteRevision, selectedEntry } = noteHistoryController
const remoteHistoryListRef = useRef<HTMLDivElement>(null)
useListKeyboardNavigation(remoteHistoryListRef)
const remoteHistoryLength = useMemo(() => remoteHistory?.map((group) => group.entries).flat().length, [remoteHistory])
const [selectedEntryUuid, setSelectedEntryUuid] = useState('')
const firstEntry = useMemo(() => {
return remoteHistory?.find((group) => group.entries?.length)?.entries?.[0]
}, [remoteHistory])
const selectFirstEntry = useCallback(() => {
if (firstEntry) {
setSelectedEntryUuid(firstEntry.uuid)
fetchAndSetRemoteRevision(firstEntry).catch(console.error)
}
}, [fetchAndSetRemoteRevision, firstEntry])
useEffect(() => {
if (firstEntry && !selectedEntryUuid.length) {
selectFirstEntry()
}
}, [fetchAndSetRemoteRevision, firstEntry, remoteHistory, selectFirstEntry, selectedEntryUuid.length])
return (
<div
className={`flex flex-col w-full h-full focus:shadow-none ${
@@ -63,15 +39,14 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
{group.entries.map((entry) => (
<HistoryListItem
key={entry.uuid}
isSelected={selectedEntryUuid === entry.uuid}
isSelected={(selectedEntry as RevisionListEntry)?.uuid === entry.uuid}
onClick={() => {
setSelectedEntryUuid(entry.uuid)
fetchAndSetRemoteRevision(entry).catch(console.error)
selectRemoteRevision(entry)
}}
>
<div className="flex flex-grow items-center justify-between">
<div>{previewHistoryEntryTitle(entry)}</div>
{!application.features.hasMinimumRole(entry.required_role) && <Icon type="premium-feature" />}
{!features.hasMinimumRole(entry.required_role) && <Icon type="premium-feature" />}
</div>
</HistoryListItem>
))}
@@ -1,8 +1,8 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { HistoryLockedIllustration } from '@standardnotes/icons'
import Button from '@/Components/Button/Button'
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
const getPlanHistoryDuration = (planName: string | undefined) => {
switch (planName) {
@@ -20,12 +20,11 @@ const getPremiumContentCopy = (planName: string | undefined) => {
}
type Props = {
viewControllerManager: ViewControllerManager
subscriptionController: SubscriptionController
}
const RevisionContentLocked: FunctionComponent<Props> = ({ viewControllerManager }) => {
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } =
viewControllerManager.subscriptionController
const RevisionContentLocked: FunctionComponent<Props> = ({ subscriptionController }) => {
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } = subscriptionController
return (
<div className="flex w-full h-full items-center justify-center">
@@ -0,0 +1,32 @@
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import HistoryModalDialogContent from './HistoryModalDialogContent'
import HistoryModalDialog from './HistoryModalDialog'
import { RevisionHistoryModalProps } from './RevisionHistoryModalProps'
const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = ({
application,
historyModalController,
notesController,
selectionController,
subscriptionController,
}) => {
if (!historyModalController.note) {
return null
}
return (
<HistoryModalDialog onDismiss={historyModalController.dismissModal}>
<HistoryModalDialogContent
application={application}
dismissModal={historyModalController.dismissModal}
note={historyModalController.note}
notesController={notesController}
selectionController={selectionController}
subscriptionController={subscriptionController}
/>
</HistoryModalDialog>
)
}
export default observer(RevisionHistoryModal)
@@ -0,0 +1,22 @@
import { WebApplication } from '@/Application/Application'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
import { NotesController } from '@/Controllers/NotesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
import { SNNote } from '@standardnotes/snjs'
type CommonProps = {
application: WebApplication
notesController: NotesController
selectionController: SelectedItemsController
subscriptionController: SubscriptionController
}
export type RevisionHistoryModalProps = CommonProps & {
historyModalController: HistoryModalController
}
export type RevisionHistoryModalContentProps = CommonProps & {
note: SNNote
dismissModal: () => void
}
@@ -1,315 +0,0 @@
import { confirmDialog } from '@/Services/AlertService'
import { STRING_RESTORE_LOCKED_ATTEMPT } from '@/Constants/Strings'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { getPlatformString } from '@/Utils'
import { DialogContent, DialogOverlay } from '@reach/dialog'
import {
ButtonType,
ContentType,
HistoryEntry,
PayloadEmitSource,
RevisionListEntry,
SNNote,
} from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import Button from '@/Components/Button/Button'
import HistoryListContainer from './HistoryListContainer'
import RevisionContentLocked from './RevisionContentLocked'
import SelectedRevisionContent from './SelectedRevisionContent'
import { LegacyHistoryEntry, RemoteRevisionListGroup, sortRevisionListIntoGroups } from './utils'
type RevisionHistoryModalProps = {
application: WebApplication
viewControllerManager: ViewControllerManager
}
const ABSOLUTE_CENTER_CLASSNAME = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
type RevisionContentPlaceholderProps = {
isFetchingSelectedRevision: boolean
selectedRevision: HistoryEntry | LegacyHistoryEntry | undefined
showContentLockedScreen: boolean
}
const RevisionContentPlaceholder: FunctionComponent<RevisionContentPlaceholderProps> = ({
isFetchingSelectedRevision,
selectedRevision,
showContentLockedScreen,
}) => (
<div
className={`absolute w-full h-full top-0 left-0 ${
(isFetchingSelectedRevision || !selectedRevision) && !showContentLockedScreen
? 'z-index-1 bg-default'
: '-z-index-1'
}`}
>
{isFetchingSelectedRevision && <div className={`sk-spinner w-5 h-5 spinner-info ${ABSOLUTE_CENTER_CLASSNAME}`} />}
{!isFetchingSelectedRevision && !selectedRevision ? (
<div className={`color-passive-0 select-none ${ABSOLUTE_CENTER_CLASSNAME}`}>No revision selected</div>
) : null}
</div>
)
export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = observer(
({ application, viewControllerManager }) => {
const closeButtonRef = useRef<HTMLButtonElement>(null)
const dismissModal = useCallback(() => {
viewControllerManager.notesController.setShowRevisionHistoryModal(false)
}, [viewControllerManager.notesController])
const note = viewControllerManager.notesController.firstSelectedNote
const editorForCurrentNote = useMemo(() => {
if (note) {
return application.componentManager.editorForNote(note)
} else {
return undefined
}
}, [application, note])
const [isFetchingSelectedRevision, setIsFetchingSelectedRevision] = useState(false)
const [selectedRevision, setSelectedRevision] = useState<HistoryEntry | LegacyHistoryEntry>()
const [selectedRemoteEntry, setSelectedRemoteEntry] = useState<RevisionListEntry>()
const [isDeletingRevision, setIsDeletingRevision] = useState(false)
const [templateNoteForRevision, setTemplateNoteForRevision] = useState<SNNote>()
const [showContentLockedScreen, setShowContentLockedScreen] = useState(false)
const [remoteHistory, setRemoteHistory] = useState<RemoteRevisionListGroup[]>()
const [isFetchingRemoteHistory, setIsFetchingRemoteHistory] = useState(false)
const fetchRemoteHistory = useCallback(async () => {
if (note) {
setRemoteHistory(undefined)
setIsFetchingRemoteHistory(true)
try {
const initialRemoteHistory = await application.historyManager.remoteHistoryForItem(note)
const remoteHistoryAsGroups = sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory)
setRemoteHistory(remoteHistoryAsGroups)
} catch (err) {
console.error(err)
} finally {
setIsFetchingRemoteHistory(false)
}
}
}, [application, note])
useEffect(() => {
if (!remoteHistory?.length) {
fetchRemoteHistory().catch(console.error)
}
}, [fetchRemoteHistory, remoteHistory?.length])
const restore = useCallback(() => {
if (selectedRevision) {
const originalNote = application.items.findItem(selectedRevision.payload.uuid) as SNNote
if (originalNote.locked) {
application.alertService.alert(STRING_RESTORE_LOCKED_ATTEMPT).catch(console.error)
return
}
confirmDialog({
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
confirmButtonStyle: 'danger',
})
.then((confirmed) => {
if (confirmed) {
application.mutator
.changeAndSaveItem(
originalNote,
(mutator) => {
mutator.setCustomContent(selectedRevision.payload.content)
},
true,
PayloadEmitSource.RemoteRetrieved,
)
.catch(console.error)
dismissModal()
}
})
.catch(console.error)
}
}, [application.alertService, application.items, application.mutator, dismissModal, selectedRevision])
const restoreAsCopy = useCallback(async () => {
if (selectedRevision) {
const originalNote = application.items.findSureItem<SNNote>(selectedRevision.payload.uuid)
const duplicatedItem = await application.mutator.duplicateItem(originalNote, {
...selectedRevision.payload.content,
title: selectedRevision.payload.content.title
? selectedRevision.payload.content.title + ' (copy)'
: undefined,
})
viewControllerManager.selectionController.selectItem(duplicatedItem.uuid).catch(console.error)
dismissModal()
}
}, [
viewControllerManager.selectionController,
application.items,
application.mutator,
dismissModal,
selectedRevision,
])
useEffect(() => {
const fetchTemplateNote = async () => {
if (selectedRevision) {
const newTemplateNote = application.mutator.createTemplateItem(
ContentType.Note,
selectedRevision.payload.content,
) as SNNote
setTemplateNoteForRevision(newTemplateNote)
}
}
fetchTemplateNote().catch(console.error)
}, [application, selectedRevision])
const deleteSelectedRevision = useCallback(() => {
if (!selectedRemoteEntry) {
return
}
application.alertService
.confirm(
'Are you sure you want to delete this revision?',
'Delete revision?',
'Delete revision',
ButtonType.Danger,
'Cancel',
)
.then((shouldDelete) => {
if (shouldDelete && note) {
setIsDeletingRevision(true)
application.historyManager
.deleteRemoteRevision(note, selectedRemoteEntry)
.then((res) => {
if (res.error?.message) {
throw new Error(res.error.message)
}
fetchRemoteHistory().catch(console.error)
setIsDeletingRevision(false)
})
.catch(console.error)
}
})
.catch(console.error)
}, [application.alertService, application.historyManager, fetchRemoteHistory, note, selectedRemoteEntry])
return (
<DialogOverlay
className={`sn-component ${getPlatformString()}`}
onDismiss={dismissModal}
initialFocusRef={closeButtonRef}
aria-label="Note revision history"
>
<DialogContent
aria-label="Note revision history"
className="rounded shadow-overlay"
style={{
width: '90%',
maxWidth: '90%',
minHeight: '90%',
background: 'var(--modal-background-color)',
}}
>
<div
className={`bg-default flex flex-col h-full overflow-hidden ${
isDeletingRevision ? 'pointer-events-none cursor-not-allowed' : ''
}`}
>
<div className="flex flex-grow min-h-0">
{note && (
<HistoryListContainer
application={application}
note={note}
remoteHistory={remoteHistory}
isFetchingRemoteHistory={isFetchingRemoteHistory}
setSelectedRevision={setSelectedRevision}
setSelectedRemoteEntry={setSelectedRemoteEntry}
setShowContentLockedScreen={setShowContentLockedScreen}
setIsFetchingSelectedRevision={setIsFetchingSelectedRevision}
/>
)}
<div className={'flex flex-col flex-grow relative'}>
<RevisionContentPlaceholder
selectedRevision={selectedRevision}
isFetchingSelectedRevision={isFetchingSelectedRevision}
showContentLockedScreen={showContentLockedScreen}
/>
{showContentLockedScreen && !selectedRevision && (
<RevisionContentLocked viewControllerManager={viewControllerManager} />
)}
{selectedRevision && templateNoteForRevision && (
<SelectedRevisionContent
application={application}
viewControllerManager={viewControllerManager}
selectedRevision={selectedRevision}
editorForCurrentNote={editorForCurrentNote}
templateNoteForRevision={templateNoteForRevision}
/>
)}
</div>
</div>
<div className="flex flex-shrink-0 justify-between items-center min-h-6 px-2.5 py-2 border-0 border-t-1px border-solid border-main">
<div>
<Button
className="py-1.35"
label="Close"
onClick={dismissModal}
ref={closeButtonRef}
variant="normal"
/>
</div>
{selectedRevision && (
<div className="flex items-center">
{selectedRemoteEntry && (
<Button className="py-1.35 mr-2.5" onClick={deleteSelectedRevision} variant="normal">
{isDeletingRevision ? (
<div className="sk-spinner my-1 w-3 h-3 spinner-info" />
) : (
'Delete this revision'
)}
</Button>
)}
<Button
className="py-1.35 mr-2.5"
label="Restore as a copy"
onClick={restoreAsCopy}
variant="normal"
/>
<Button className="py-1.35" label="Restore version" onClick={restore} variant="primary" />
</div>
)}
</div>
</div>
</DialogContent>
</DialogOverlay>
)
},
)
RevisionHistoryModal.displayName = 'RevisionHistoryModal'
const RevisionHistoryModalWrapper: FunctionComponent<RevisionHistoryModalProps> = ({
application,
viewControllerManager,
}) => {
if (!viewControllerManager.notesController.showRevisionHistoryModal) {
return null
}
return <RevisionHistoryModal application={application} viewControllerManager={viewControllerManager} />
}
export default observer(RevisionHistoryModalWrapper)
@@ -0,0 +1,5 @@
export enum RevisionType {
Session = 'Session',
Remote = 'Remote',
Legacy = 'Legacy',
}
@@ -1,39 +1,45 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { HistoryEntry, SNComponent, SNNote } from '@standardnotes/snjs'
import { ContentType, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useEffect, useMemo } from 'react'
import ComponentView from '@/Components/ComponentView/ComponentView'
import { LegacyHistoryEntry } from './utils'
import { NotesController } from '@/Controllers/NotesController'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
const ABSOLUTE_CENTER_CLASSNAME = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
type SelectedRevisionContentProps = {
application: WebApplication
viewControllerManager: ViewControllerManager
selectedRevision: HistoryEntry | LegacyHistoryEntry
editorForCurrentNote: SNComponent | undefined
templateNoteForRevision: SNNote
noteHistoryController: NoteHistoryController
notesController: NotesController
}
const SelectedRevisionContent: FunctionComponent<SelectedRevisionContentProps> = ({
application,
viewControllerManager,
selectedRevision,
editorForCurrentNote,
templateNoteForRevision,
noteHistoryController,
notesController,
}) => {
const note = notesController.firstSelectedNote
const { selectedRevision } = noteHistoryController
const componentViewer = useMemo(() => {
const editorForCurrentNote = note ? application.componentManager.editorForNote(note) : undefined
if (!editorForCurrentNote) {
return undefined
}
const templateNoteForRevision = application.mutator.createTemplateItem(
ContentType.Note,
selectedRevision?.payload.content,
) as SNNote
const componentViewer = application.componentManager.createComponentViewer(editorForCurrentNote)
componentViewer.setReadonly(true)
componentViewer.lockReadonly = true
componentViewer.overrideContextItem = templateNoteForRevision
return componentViewer
}, [application, editorForCurrentNote, templateNoteForRevision])
}, [application.componentManager, application.mutator, note, selectedRevision?.payload.content])
useEffect(() => {
return () => {
@@ -46,17 +52,16 @@ const SelectedRevisionContent: FunctionComponent<SelectedRevisionContentProps> =
return (
<div className="flex flex-col h-full overflow-hidden">
<div className="p-4 text-base font-bold w-full">
<div className="title">{selectedRevision.payload.content.title}</div>
<div className="title">{selectedRevision?.payload.content.title}</div>
</div>
{!componentViewer && (
<div className="relative flex-grow min-h-0 overflow-hidden">
{selectedRevision.payload.content.text.length ? (
{selectedRevision?.payload.content.text.length ? (
<textarea
readOnly={true}
className="w-full h-full resize-none p-4 pt-0 border-0 bg-default color-text text-editor font-editor"
>
{selectedRevision.payload.content.text}
</textarea>
value={selectedRevision?.payload.content.text}
/>
) : (
<div className={`color-passive-0 ${ABSOLUTE_CENTER_CLASSNAME}`}>Empty note.</div>
)}
@@ -64,12 +69,7 @@ const SelectedRevisionContent: FunctionComponent<SelectedRevisionContentProps> =
)}
{componentViewer && (
<div className="component-view">
<ComponentView
key={componentViewer.identifier}
componentViewer={componentViewer}
application={application}
viewControllerManager={viewControllerManager}
/>
<ComponentView key={componentViewer.identifier} componentViewer={componentViewer} application={application} />
</div>
)}
</div>
@@ -1,60 +1,25 @@
import { HistoryEntry, NoteHistoryEntry, RevisionListEntry } from '@standardnotes/snjs'
import {
Dispatch,
Fragment,
FunctionComponent,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { LegacyHistoryEntry, ListGroup } from './utils'
import { observer } from 'mobx-react-lite'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
type Props = {
sessionHistory: ListGroup<NoteHistoryEntry>[]
setSelectedRevision: Dispatch<SetStateAction<HistoryEntry | LegacyHistoryEntry | undefined>>
setSelectedRemoteEntry: Dispatch<SetStateAction<RevisionListEntry | undefined>>
noteHistoryController: NoteHistoryController
}
const SessionHistoryList: FunctionComponent<Props> = ({
sessionHistory,
setSelectedRevision,
setSelectedRemoteEntry,
}) => {
const SessionHistoryList: FunctionComponent<Props> = ({ noteHistoryController }) => {
const { sessionHistory, selectedRevision, selectSessionRevision } = noteHistoryController
const sessionHistoryListRef = useRef<HTMLDivElement>(null)
useListKeyboardNavigation(sessionHistoryListRef)
const sessionHistoryLength = useMemo(
() => sessionHistory.map((group) => group.entries).flat().length,
() => sessionHistory?.map((group) => group.entries).flat().length,
[sessionHistory],
)
const [selectedItemCreatedAt, setSelectedItemCreatedAt] = useState<Date>()
const firstEntry = useMemo(() => {
return sessionHistory?.find((group) => group.entries?.length)?.entries?.[0]
}, [sessionHistory])
const selectFirstEntry = useCallback(() => {
if (firstEntry) {
setSelectedItemCreatedAt(firstEntry.payload.created_at)
setSelectedRevision(firstEntry)
}
}, [firstEntry, setSelectedRevision])
useEffect(() => {
if (firstEntry && !selectedItemCreatedAt) {
selectFirstEntry()
} else if (!firstEntry) {
setSelectedRevision(undefined)
}
}, [firstEntry, selectFirstEntry, selectedItemCreatedAt, setSelectedRevision])
return (
<div
className={`flex flex-col w-full h-full focus:shadow-none ${
@@ -72,11 +37,9 @@ const SessionHistoryList: FunctionComponent<Props> = ({
{group.entries.map((entry, index) => (
<HistoryListItem
key={index}
isSelected={selectedItemCreatedAt === entry.payload.created_at}
isSelected={selectedRevision?.payload.created_at === entry.payload.created_at}
onClick={() => {
setSelectedItemCreatedAt(entry.payload.created_at)
setSelectedRevision(entry)
setSelectedRemoteEntry(undefined)
selectSessionRevision(entry)
}}
>
{entry.previewTitle()}
@@ -93,4 +56,4 @@ const SessionHistoryList: FunctionComponent<Props> = ({
)
}
export default SessionHistoryList
export default observer(SessionHistoryList)
@@ -1,17 +1,15 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite'
import Bubble from '@/Components/Bubble/Bubble'
import { useCallback } from 'react'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
type Props = {
viewControllerManager: ViewControllerManager
application: WebApplication
searchOptions: SearchOptionsController
}
const SearchOptions = ({ viewControllerManager }: Props) => {
const { searchOptionsController: searchOptions } = viewControllerManager
const SearchOptions = ({ searchOptions }: Props) => {
const { includeProtectedContents, includeArchived, includeTrashed } = searchOptions
const toggleIncludeProtectedContents = useCallback(async () => {
@@ -5,4 +5,5 @@ export const ElementIds = {
FileTextPreview: 'file-text-preview',
EditorContent: 'editor-content',
EditorColumn: 'editor-column',
ContentList: 'notes-scrollable',
}
@@ -215,6 +215,10 @@ export class ItemListController extends AbstractViewController implements Intern
}
}
public get listLength() {
return this.renderedItems.length
}
public getActiveItemController(): NoteViewController | FileViewController | undefined {
return this.application.itemControllerGroup.activeItemViewController
}
@@ -0,0 +1,34 @@
import { WebApplication } from '@/Application/Application'
import { InternalEventBus, SNNote } from '@standardnotes/snjs'
import { action, makeObservable, observable } from 'mobx'
import { AbstractViewController } from '../Abstract/AbstractViewController'
export class HistoryModalController extends AbstractViewController {
note?: SNNote = undefined
override deinit(): void {
super.deinit()
this.note = undefined
}
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
note: observable,
setNote: action,
})
}
setNote = (note: SNNote | undefined) => {
this.note = note
}
openModal = (note: SNNote | undefined) => {
this.setNote(note)
}
dismissModal = () => {
this.setNote(undefined)
}
}
@@ -0,0 +1,368 @@
import { WebApplication } from '@/Application/Application'
import { RevisionType } from '@/Components/RevisionHistoryModal/RevisionType'
import {
LegacyHistoryEntry,
ListGroup,
RemoteRevisionListGroup,
sortRevisionListIntoGroups,
} from '@/Components/RevisionHistoryModal/utils'
import { STRING_RESTORE_LOCKED_ATTEMPT } from '@/Constants/Strings'
import { confirmDialog } from '@/Services/AlertService'
import {
Action,
ActionVerb,
ButtonType,
HistoryEntry,
NoteHistoryEntry,
PayloadEmitSource,
RevisionListEntry,
SNNote,
} from '@standardnotes/snjs'
import { makeObservable, observable, action } from 'mobx'
import { SelectedItemsController } from '../SelectedItemsController'
type RemoteHistory = RemoteRevisionListGroup[]
type SessionHistory = ListGroup<NoteHistoryEntry>[]
type LegacyHistory = Action[]
type SelectedRevision = HistoryEntry | LegacyHistoryEntry | undefined
type SelectedEntry = RevisionListEntry | NoteHistoryEntry | Action | undefined
export enum RevisionContentState {
Idle,
Loading,
Loaded,
NotEntitled,
}
export class NoteHistoryController {
remoteHistory: RemoteHistory = []
isFetchingRemoteHistory = false
sessionHistory: SessionHistory = []
legacyHistory: LegacyHistory = []
selectedRevision: SelectedRevision = undefined
selectedEntry: SelectedEntry = undefined
contentState = RevisionContentState.Idle
currentTab = RevisionType.Remote
constructor(
private application: WebApplication,
private note: SNNote,
private selectionController: SelectedItemsController,
) {
void this.fetchAllHistory()
makeObservable(this, {
selectedRevision: observable,
setSelectedRevision: action,
selectedEntry: observable,
setSelectedEntry: action,
remoteHistory: observable,
setRemoteHistory: action,
isFetchingRemoteHistory: observable,
setIsFetchingRemoteHistory: action,
sessionHistory: observable,
setSessionHistory: action,
legacyHistory: observable,
setLegacyHistory: action,
resetHistoryState: action,
currentTab: observable,
selectTab: action,
contentState: observable,
setContentState: action,
})
}
setSelectedRevision = (revision: SelectedRevision) => {
this.selectedRevision = revision
}
setSelectedEntry = (entry: SelectedEntry) => {
this.selectedEntry = entry
}
clearSelection = () => {
this.setSelectedEntry(undefined)
this.setSelectedRevision(undefined)
}
selectTab = (tab: RevisionType) => {
this.currentTab = tab
this.clearSelection()
this.setContentState(RevisionContentState.Idle)
this.selectFirstRevision()
}
setIsFetchingRemoteHistory = (value: boolean) => {
this.isFetchingRemoteHistory = value
}
setContentState = (contentState: RevisionContentState) => {
this.contentState = contentState
}
selectRemoteRevision = async (entry: RevisionListEntry) => {
if (!this.note) {
return
}
if (!this.application.features.hasMinimumRole(entry.required_role)) {
this.setContentState(RevisionContentState.NotEntitled)
this.setSelectedRevision(undefined)
return
}
this.setContentState(RevisionContentState.Loading)
this.clearSelection()
try {
this.setSelectedEntry(entry)
const remoteRevision = await this.application.historyManager.fetchRemoteRevision(this.note, entry)
this.setSelectedRevision(remoteRevision)
} catch (err) {
this.clearSelection()
console.error(err)
} finally {
this.setContentState(RevisionContentState.Loaded)
}
}
selectLegacyRevision = async (entry: Action) => {
this.clearSelection()
this.setContentState(RevisionContentState.Loading)
if (!this.note) {
return
}
try {
if (!entry.subactions?.[0]) {
throw new Error('Could not find revision action url')
}
this.setSelectedEntry(entry)
const response = await this.application.actionsManager.runAction(entry.subactions[0], this.note)
if (!response) {
throw new Error('Could not fetch revision')
}
this.setSelectedRevision(response.item as unknown as HistoryEntry)
} catch (error) {
console.error(error)
this.setSelectedRevision(undefined)
} finally {
this.setContentState(RevisionContentState.Loaded)
}
}
selectSessionRevision = (entry: NoteHistoryEntry) => {
this.clearSelection()
this.setSelectedEntry(entry)
this.setSelectedRevision(entry)
}
private get flattenedRemoteHistory() {
return this.remoteHistory.map((group) => group.entries).flat()
}
private get flattenedSessionHistory() {
return this.sessionHistory.map((group) => group.entries).flat()
}
selectFirstRevision = () => {
switch (this.currentTab) {
case RevisionType.Remote: {
const firstEntry = this.flattenedRemoteHistory[0]
if (firstEntry) {
void this.selectRemoteRevision(firstEntry)
}
break
}
case RevisionType.Session: {
const firstEntry = this.flattenedSessionHistory[0]
if (firstEntry) {
void this.selectSessionRevision(firstEntry)
}
break
}
case RevisionType.Legacy: {
const firstEntry = this.legacyHistory[0]
if (firstEntry) {
void this.selectLegacyRevision(firstEntry)
}
break
}
}
}
selectPrevOrNextRemoteRevision = (revisionEntry: RevisionListEntry) => {
const currentIndex = this.flattenedRemoteHistory.findIndex((entry) => entry?.uuid === revisionEntry.uuid)
const previousEntry = this.flattenedRemoteHistory[currentIndex - 1]
const nextEntry = this.flattenedRemoteHistory[currentIndex + 1]
if (previousEntry) {
void this.selectRemoteRevision(previousEntry)
} else if (nextEntry) {
void this.selectRemoteRevision(nextEntry)
}
}
setRemoteHistory = (remoteHistory: RemoteHistory) => {
this.remoteHistory = remoteHistory
}
fetchRemoteHistory = async () => {
this.setRemoteHistory([])
if (this.note) {
this.setIsFetchingRemoteHistory(true)
try {
const initialRemoteHistory = await this.application.historyManager.remoteHistoryForItem(this.note)
this.setRemoteHistory(sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory))
} catch (err) {
console.error(err)
} finally {
this.setIsFetchingRemoteHistory(false)
}
}
}
setLegacyHistory = (legacyHistory: LegacyHistory) => {
this.legacyHistory = legacyHistory
}
fetchLegacyHistory = async () => {
const actionExtensions = this.application.actionsManager.getExtensions()
actionExtensions.forEach(async (ext) => {
if (!this.note) {
return
}
const actionExtension = await this.application.actionsManager.loadExtensionInContextOfItem(ext, this.note)
if (!actionExtension) {
return
}
const isLegacyNoteHistoryExt = actionExtension?.actions.some((action) => action.verb === ActionVerb.Nested)
if (!isLegacyNoteHistoryExt) {
return
}
this.setLegacyHistory(actionExtension.actions.filter((action) => action.subactions?.[0]))
})
}
setSessionHistory = (sessionHistory: SessionHistory) => {
this.sessionHistory = sessionHistory
}
fetchAllHistory = async () => {
this.resetHistoryState()
if (!this.note) {
return
}
this.setSessionHistory(
sortRevisionListIntoGroups<NoteHistoryEntry>(
this.application.historyManager.sessionHistoryForItem(this.note) as NoteHistoryEntry[],
),
)
await this.fetchRemoteHistory()
await this.fetchLegacyHistory()
this.selectFirstRevision()
}
resetHistoryState = () => {
this.remoteHistory = []
this.sessionHistory = []
this.legacyHistory = []
}
restoreRevision = async (revision: NonNullable<SelectedRevision>) => {
const originalNote = this.application.items.findItem<SNNote>(revision.payload.uuid)
if (originalNote?.locked) {
this.application.alertService.alert(STRING_RESTORE_LOCKED_ATTEMPT).catch(console.error)
return
}
const didConfirm = await confirmDialog({
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
confirmButtonStyle: 'danger',
})
if (!originalNote) {
throw new Error('Original note not found.')
}
if (didConfirm) {
void this.application.mutator.changeAndSaveItem(
originalNote,
(mutator) => {
mutator.setCustomContent(revision.payload.content)
},
true,
PayloadEmitSource.RemoteRetrieved,
)
}
}
restoreRevisionAsCopy = async (revision: NonNullable<SelectedRevision>) => {
const originalNote = this.application.items.findSureItem<SNNote>(revision.payload.uuid)
const duplicatedItem = await this.application.mutator.duplicateItem(originalNote, {
...revision.payload.content,
title: revision.payload.content.title ? revision.payload.content.title + ' (copy)' : undefined,
})
this.selectionController.selectItem(duplicatedItem.uuid).catch(console.error)
}
deleteRemoteRevision = async (revisionEntry: RevisionListEntry) => {
const shouldDelete = await this.application.alertService.confirm(
'Are you sure you want to delete this revision?',
'Delete revision?',
'Delete revision',
ButtonType.Danger,
'Cancel',
)
if (!shouldDelete || !this.note) {
return
}
const response = await this.application.historyManager.deleteRemoteRevision(this.note, revisionEntry)
if (response.error?.message) {
throw new Error(response.error.message)
}
this.clearSelection()
this.selectPrevOrNextRemoteRevision(revisionEntry)
await this.fetchRemoteHistory()
}
}
@@ -21,7 +21,6 @@ export class NotesController extends AbstractViewController {
contextMenuClickLocation: { x: number; y: number } = { x: 0, y: 0 }
contextMenuMaxHeight: number | 'auto' = 'auto'
showProtectedWarning = false
showRevisionHistoryModal = false
private itemListController!: ItemListController
override deinit() {
@@ -48,7 +47,6 @@ export class NotesController extends AbstractViewController {
contextMenuOpen: observable,
contextMenuPosition: observable,
showProtectedWarning: observable,
showRevisionHistoryModal: observable,
selectedNotes: computed,
firstSelectedNote: computed,
@@ -60,7 +58,6 @@ export class NotesController extends AbstractViewController {
setContextMenuPosition: action,
setContextMenuMaxHeight: action,
setShowProtectedWarning: action,
setShowRevisionHistoryModal: action,
unselectNotes: action,
})
}
@@ -367,8 +364,4 @@ export class NotesController extends AbstractViewController {
private getSelectedNotesList(): SNNote[] {
return Object.values(this.selectedNotes)
}
setShowRevisionHistoryModal(show: boolean): void {
this.showRevisionHistoryModal = show
}
}

Some files were not shown because too many files have changed in this diff Show More