mirror of
https://github.com/standardnotes/app
synced 2026-09-19 09:13:53 -04:00
Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e8ce4bee6 | ||
|
|
29368c51b8 | ||
|
|
621bf1b810 | ||
|
|
28e43d37c0 | ||
|
|
e07fed267f | ||
|
|
1726302615 | ||
|
|
73ca12275f | ||
|
|
1ee6be4e5c | ||
|
|
42fc1d073d | ||
|
|
c32541c6e1 | ||
|
|
34d9100af6 | ||
|
|
7f2074a6ec | ||
|
|
0e95b451d6 | ||
|
|
0e886bf0f1 | ||
|
|
9b3f11d85b | ||
|
|
349fd7204e | ||
|
|
afa9aafa3e | ||
|
|
b57786eeeb | ||
|
|
f30d0898ec | ||
|
|
1729bc4292 | ||
|
|
abe2b17cb4 | ||
|
|
d51de34daa | ||
|
|
ec0f0a2b18 | ||
|
|
a417e5ba8d | ||
|
|
02eb969e07 | ||
|
|
767d354780 | ||
|
|
052e7d2f90 | ||
|
|
e3ec8503a5 | ||
|
|
cc2762a29d | ||
|
|
96e8dfdd31 | ||
|
|
60cbd7aed2 | ||
|
|
2b0c9b188c | ||
|
|
4181a4cca0 | ||
|
|
d51ab36bbf | ||
|
|
81774d74c6 | ||
|
|
148aee441b | ||
|
|
db3069eab3 | ||
|
|
c045eb1459 | ||
|
|
e19184c2da | ||
|
|
7f6c53dfcb | ||
|
|
1c9ad49aea | ||
|
|
6fc1bb5e84 | ||
|
|
ddd7c8f889 | ||
|
|
03eb1cc6b4 | ||
|
|
ac91621271 | ||
|
|
c01fa7bb99 | ||
|
|
6db6079790 | ||
|
|
2026d8b936 | ||
|
|
645602b9d3 | ||
|
|
3c91b0cb17 | ||
|
|
8145cdb8ac | ||
|
|
d50c5fefcb | ||
|
|
745e850a5c | ||
|
|
cadf03f0ef | ||
|
|
931b674e4e | ||
|
|
a6efc5336b | ||
|
|
21611d9b45 | ||
|
|
9b91875d3d | ||
|
|
dcc8cfbe45 | ||
|
|
ff1d71c2f8 | ||
|
|
81b5123294 | ||
|
|
7a89a3cd9c | ||
|
|
08fd234e60 | ||
|
|
0fac2dd939 | ||
|
|
212e7843ec | ||
|
|
6bbf0c02bd | ||
|
|
2485bed350 | ||
|
|
2e517c985d | ||
|
|
75303b5eb8 | ||
|
|
b5997aa70e | ||
|
|
07e26915b0 | ||
|
|
4661c6cb77 | ||
|
|
0d1e0d730d | ||
|
|
7ef0db545c | ||
|
|
9f6316976c | ||
|
|
0a01ddb430 | ||
|
|
c40b17bd4c | ||
|
|
858fa6835c | ||
|
|
96babc454d | ||
|
|
8579ff39b1 | ||
|
|
99163d90d2 | ||
|
|
8a709158d6 | ||
|
|
25cf4cfd22 | ||
|
|
9f8e937766 | ||
|
|
9d5a57521e | ||
|
|
e36a1ffea0 | ||
|
|
b05c793fe9 | ||
|
|
c45417623c | ||
|
|
6704c6dd4e | ||
|
|
87cd31ae5d |
@@ -3,7 +3,7 @@ name: Desktop Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/*desktop*'
|
||||
- '*standardnotes/*web*'
|
||||
- '!*standardnotes/*inner-desktop*'
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ jobs:
|
||||
cache: 'yarn'
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: Compile Mac
|
||||
run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- name: MacX64
|
||||
run: |
|
||||
yarn run electron-builder --mac --x64 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
node scripts/fixMacZip.js
|
||||
node scripts/fixMacZip.js ${{ env.APP_VERSION }}
|
||||
- name: MacArm64
|
||||
run: yarn run electron-builder --mac --arm64 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: Compile for AppImage
|
||||
run: yarn run webpack --config desktop.webpack.prod.js
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
- run: yarn run electron-builder --windows --x64 --ia32 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
- name: Upload
|
||||
@@ -160,6 +160,7 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -167,6 +168,8 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
@@ -194,8 +197,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
tag_name: "@standardnotes/desktop@${{ steps.package-version.outputs.current-version}}"
|
||||
name: "Desktop ${{ steps.package-version.outputs.current-version }}"
|
||||
tag_name: "@standardnotes/desktop@${{ env.APP_VERSION }}"
|
||||
name: "Desktop ${{ env.APP_VERSION }}"
|
||||
body: ${{ steps.release-notes.outputs.result }}
|
||||
prerelease: true
|
||||
draft: false
|
||||
@@ -214,4 +217,4 @@ jobs:
|
||||
sudo snap install snapcraft --classic
|
||||
echo "${{ secrets.SNAPCRAFT_LOGIN_FILE }}" >> snapauth.txt
|
||||
snapcraft login --with=snapauth.txt
|
||||
snapcraft upload dist/standard-notes-${{ steps.package-version.outputs.current-version}}-linux-amd64.snap --release stable,candidate,beta,edge
|
||||
snapcraft upload dist/standard-notes-${{ env.APP_VERSION }}-linux-amd64.snap --release stable,candidate,beta,edge
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build:desktop
|
||||
- run: yarn run webpack --config desktop.webpack.prod.js
|
||||
- run: echo APP_VERSION=$(node -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
- run: yarn run electron-builder --windows --x64 --ia32 --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
|
||||
|
||||
- name: Upload
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Mobile Release Production
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/mobile*'
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Production Android keystore
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable && yarn install:pods
|
||||
- run: yarn build:mobile
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Mobile TestFlight
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/mobile*'
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Dev Android keystore
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@
|
||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
"version": "yarn install --no-immutable && git add yarn.lock",
|
||||
"version": "yarn install --no-immutable && git add yarn.lock && yarn changelog:json",
|
||||
"changelog:json": "node scripts/ChangelogToJson.js && git add .",
|
||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||
"workspace:list": " yarn lerna list -all",
|
||||
"upgrade:snjs": "ncu -u '@standardnotes/*' && yarn workspaces foreach --verbose run upgrade:snjs"
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.9",
|
||||
"version": "1.20.16",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Disable all-caps for H2/H3 headings in Super notes ([9b3f11d](https://github.com/standardnotes/app/commit/9b3f11d85b10914a03aab93803513766670c47fe))
|
||||
* Super note block drag-n-drop on mobile ([#2072](https://github.com/standardnotes/app/issues/2072)) ([349fd72](https://github.com/standardnotes/app/commit/349fd7204e209d56b720a3b0cc6b2bfc0fc50f47))
|
||||
|
||||
## [1.9.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.9.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
## [1.9.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make super note styling consistent with plaintext notes ([#2068](https://github.com/standardnotes/app/issues/2068)) ([a417e5b](https://github.com/standardnotes/app/commit/a417e5ba8d3fc224b1527d0a9cfb0f25d197f9de))
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Only autofocus super notes when creating new note ([#2063](https://github.com/standardnotes/app/issues/2063)) ([96e8dfd](https://github.com/standardnotes/app/commit/96e8dfdd310fad88a360c4ac984b376b51a618d1))
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
# [1.9.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* dim checked list items in super editor ([#2046](https://github.com/standardnotes/app/issues/2046)) ([8a70915](https://github.com/standardnotes/app/commit/8a709158d6839b9eb1092923a14b0dcadb0da25b))
|
||||
* option to show markdown preview for super notes (skip e2e) ([#2048](https://github.com/standardnotes/app/issues/2048)) ([8579ff3](https://github.com/standardnotes/app/commit/8579ff39b1f81dbbcf22b4031ede183ab4287262))
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* use line height preference in super editor (skip e2e) ([#2045](https://github.com/standardnotes/app/issues/2045)) ([87cd31a](https://github.com/standardnotes/app/commit/87cd31ae5d630a528ce6d4895b2c144bf51808f0))
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/blocks-editor",
|
||||
"version": "1.7.0",
|
||||
"version": "1.9.6",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,17 +2,10 @@ import {FunctionComponent, useCallback, useState} from 'react';
|
||||
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
|
||||
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
|
||||
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
|
||||
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
|
||||
import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin';
|
||||
import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin';
|
||||
import {MarkdownShortcutPlugin} from '@lexical/react/LexicalMarkdownShortcutPlugin';
|
||||
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
|
||||
import {
|
||||
CHECK_LIST,
|
||||
ELEMENT_TRANSFORMERS,
|
||||
TEXT_FORMAT_TRANSFORMERS,
|
||||
TEXT_MATCH_TRANSFORMERS,
|
||||
} from '@lexical/markdown';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin';
|
||||
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
|
||||
@@ -30,14 +23,17 @@ import FloatingTextFormatToolbarPlugin from '../Lexical/Plugins/FloatingTextForm
|
||||
import FloatingLinkEditorPlugin from '../Lexical/Plugins/FloatingLinkEditorPlugin';
|
||||
import {truncateString} from './Utils';
|
||||
import {SuperEditorContentId} from './Constants';
|
||||
import {classNames} from '@standardnotes/utils';
|
||||
import {MarkdownTransformers} from './MarkdownTransformers';
|
||||
|
||||
type BlocksEditorProps = {
|
||||
onChange: (value: string, preview: string) => void;
|
||||
onChange?: (value: string, preview: string) => void;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
previewLength: number;
|
||||
previewLength?: number;
|
||||
spellcheck?: boolean;
|
||||
ignoreFirstChange?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
@@ -47,6 +43,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewLength,
|
||||
spellcheck,
|
||||
ignoreFirstChange = false,
|
||||
readonly,
|
||||
}) => {
|
||||
const [didIgnoreFirstChange, setDidIgnoreFirstChange] = useState(false);
|
||||
const handleChange = useCallback(
|
||||
@@ -65,11 +62,14 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
previewText += '\n';
|
||||
}
|
||||
});
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
|
||||
if (previewLength) {
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
}
|
||||
|
||||
try {
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange(stringifiedEditorState, previewText);
|
||||
onChange?.(stringifiedEditorState, previewText);
|
||||
} catch (error) {
|
||||
window.alert(
|
||||
`An invalid change was made inside the Super editor. Your change was not saved. Please report this error to the team: ${error}`,
|
||||
@@ -98,7 +98,10 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<div className="editor" ref={onRef}>
|
||||
<ContentEditable
|
||||
id={SuperEditorContentId}
|
||||
className={`ContentEditable__root overflow-y-auto ${className}`}
|
||||
className={classNames(
|
||||
'ContentEditable__root overflow-y-auto',
|
||||
className,
|
||||
)}
|
||||
spellCheck={spellcheck}
|
||||
/>
|
||||
</div>
|
||||
@@ -108,19 +111,11 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ListPlugin />
|
||||
<MarkdownShortcutPlugin
|
||||
transformers={[
|
||||
CHECK_LIST,
|
||||
...ELEMENT_TRANSFORMERS,
|
||||
...TEXT_FORMAT_TRANSFORMERS,
|
||||
...TEXT_MATCH_TRANSFORMERS,
|
||||
]}
|
||||
/>
|
||||
<MarkdownShortcutPlugin transformers={MarkdownTransformers} />
|
||||
<TablePlugin />
|
||||
<OnChangePlugin onChange={handleChange} ignoreSelectionChange={true} />
|
||||
<HistoryPlugin />
|
||||
<HorizontalRulePlugin />
|
||||
<AutoFocusPlugin />
|
||||
<ClearEditorPlugin />
|
||||
<CheckListPlugin />
|
||||
<CodeHighlightPlugin />
|
||||
@@ -130,7 +125,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<TwitterPlugin />
|
||||
<YouTubePlugin />
|
||||
<CollapsiblePlugin />
|
||||
{floatingAnchorElem && (
|
||||
{!readonly && floatingAnchorElem && (
|
||||
<>
|
||||
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
|
||||
<FloatingLinkEditorPlugin />
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
CHECK_LIST,
|
||||
ELEMENT_TRANSFORMERS,
|
||||
TEXT_FORMAT_TRANSFORMERS,
|
||||
TEXT_MATCH_TRANSFORMERS,
|
||||
} from '@lexical/markdown';
|
||||
|
||||
export const MarkdownTransformers = [
|
||||
CHECK_LIST,
|
||||
...ELEMENT_TRANSFORMERS,
|
||||
...TEXT_FORMAT_TRANSFORMERS,
|
||||
...TEXT_MATCH_TRANSFORMERS,
|
||||
];
|
||||
@@ -17,8 +17,16 @@ import {
|
||||
DRAGOVER_COMMAND,
|
||||
DROP_COMMAND,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
} from 'lexical';
|
||||
import {DragEvent as ReactDragEvent, useEffect, useRef, useState} from 'react';
|
||||
import {
|
||||
DragEvent as ReactDragEvent,
|
||||
TouchEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
import {BlockIcon} from '@standardnotes/icons';
|
||||
|
||||
@@ -26,7 +34,7 @@ import {isHTMLElement} from '../../Utils/guard';
|
||||
import {Point} from '../../Utils/point';
|
||||
import {ContainsPointReturn, Rect} from '../../Utils/rect';
|
||||
|
||||
const SPACE = 4;
|
||||
const DRAGGABLE_BLOCK_MENU_LEFT_SPACE = -2;
|
||||
const TARGET_LINE_HALF_HEIGHT = 2;
|
||||
const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
|
||||
const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
|
||||
@@ -57,11 +65,10 @@ function getTopLevelNodeKeys(editor: LexicalEditor): string[] {
|
||||
function elementContainingEventLocation(
|
||||
anchorElem: HTMLElement,
|
||||
element: HTMLElement,
|
||||
event: MouseEvent,
|
||||
eventLocation: Point,
|
||||
): {contains: ContainsPointReturn; element: HTMLElement} {
|
||||
const anchorElementRect = anchorElem.getBoundingClientRect();
|
||||
|
||||
const eventLocation = new Point(event.x, event.y);
|
||||
const elementDomRect = Rect.fromDOM(element);
|
||||
const {marginTop, marginBottom} = window.getComputedStyle(element);
|
||||
|
||||
@@ -87,7 +94,7 @@ function elementContainingEventLocation(
|
||||
const childResult = elementContainingEventLocation(
|
||||
anchorElem,
|
||||
child as HTMLElement,
|
||||
event,
|
||||
eventLocation,
|
||||
);
|
||||
|
||||
if (childResult.contains.result) {
|
||||
@@ -102,7 +109,7 @@ function elementContainingEventLocation(
|
||||
function getBlockElement(
|
||||
anchorElem: HTMLElement,
|
||||
editor: LexicalEditor,
|
||||
event: MouseEvent,
|
||||
eventLocation: Point,
|
||||
): HTMLElement | null {
|
||||
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
|
||||
|
||||
@@ -121,7 +128,7 @@ function getBlockElement(
|
||||
const {contains, element} = elementContainingEventLocation(
|
||||
anchorElem,
|
||||
elem,
|
||||
event,
|
||||
eventLocation,
|
||||
);
|
||||
|
||||
if (contains.result) {
|
||||
@@ -172,7 +179,7 @@ function setMenuPosition(
|
||||
(parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
|
||||
anchorElementRect.top;
|
||||
|
||||
const left = SPACE;
|
||||
const left = DRAGGABLE_BLOCK_MENU_LEFT_SPACE;
|
||||
|
||||
floatingElem.style.opacity = '1';
|
||||
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
|
||||
@@ -214,11 +221,12 @@ function setTargetLine(
|
||||
}
|
||||
|
||||
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
|
||||
const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE;
|
||||
const left = TEXT_BOX_HORIZONTAL_PADDING - DRAGGABLE_BLOCK_MENU_LEFT_SPACE;
|
||||
|
||||
targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
|
||||
targetLineElem.style.width = `${
|
||||
anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2
|
||||
anchorWidth -
|
||||
(TEXT_BOX_HORIZONTAL_PADDING - DRAGGABLE_BLOCK_MENU_LEFT_SPACE) * 2
|
||||
}px`;
|
||||
targetLineElem.style.opacity = '.6';
|
||||
}
|
||||
@@ -240,6 +248,7 @@ function useDraggableBlockMenu(
|
||||
const targetLineRef = useRef<HTMLDivElement>(null);
|
||||
const [draggableBlockElem, setDraggableBlockElem] =
|
||||
useState<HTMLElement | null>(null);
|
||||
const dragDataRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
@@ -253,7 +262,11 @@ function useDraggableBlockMenu(
|
||||
return;
|
||||
}
|
||||
|
||||
const _draggableBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
const _draggableBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.clientX, event.clientY),
|
||||
);
|
||||
|
||||
setDraggableBlockElem(_draggableBlockElem);
|
||||
}
|
||||
@@ -277,54 +290,13 @@ function useDraggableBlockMenu(
|
||||
}
|
||||
}, [anchorElem, draggableBlockElem]);
|
||||
|
||||
useEffect(() => {
|
||||
function onDragover(event: DragEvent): boolean {
|
||||
const [isFileTransfer] = eventFiles(event);
|
||||
if (isFileTransfer) {
|
||||
return false;
|
||||
}
|
||||
const {pageY, target} = event;
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
const targetBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
const targetLineElem = targetLineRef.current;
|
||||
if (targetBlockElem === null || targetLineElem === null) {
|
||||
return false;
|
||||
}
|
||||
setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
|
||||
// Prevent default event to be able to trigger onDrop events
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
function onDrop(event: DragEvent): boolean {
|
||||
const [isFileTransfer] = eventFiles(event);
|
||||
if (isFileTransfer) {
|
||||
return false;
|
||||
}
|
||||
const {target, dataTransfer, pageY} = event;
|
||||
const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || '';
|
||||
const draggedNode = $getNodeByKey(dragData);
|
||||
if (!draggedNode) {
|
||||
return false;
|
||||
}
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
const targetBlockElem = getBlockElement(anchorElem, editor, event);
|
||||
if (!targetBlockElem) {
|
||||
return false;
|
||||
}
|
||||
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
|
||||
|
||||
if (!targetNode) {
|
||||
return false;
|
||||
}
|
||||
if (targetNode === draggedNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const insertDraggedNode = useCallback(
|
||||
(
|
||||
draggedNode: LexicalNode,
|
||||
targetNode: LexicalNode,
|
||||
targetBlockElem: HTMLElement,
|
||||
pageY: number,
|
||||
) => {
|
||||
let nodeToInsert = draggedNode;
|
||||
const targetParent = targetNode.getParent();
|
||||
const sourceParent = draggedNode.getParent();
|
||||
@@ -342,6 +314,71 @@ function useDraggableBlockMenu(
|
||||
} else {
|
||||
targetNode.insertBefore(nodeToInsert);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function onDragover(event: DragEvent): boolean {
|
||||
const [isFileTransfer] = eventFiles(event);
|
||||
if (isFileTransfer) {
|
||||
return false;
|
||||
}
|
||||
const {pageY, target} = event;
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.pageX, pageY),
|
||||
);
|
||||
const targetLineElem = targetLineRef.current;
|
||||
if (targetBlockElem === null || targetLineElem === null) {
|
||||
return false;
|
||||
}
|
||||
setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
|
||||
// Prevent default event to be able to trigger onDrop events
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
function onDrop(event: DragEvent): boolean {
|
||||
const [isFileTransfer] = eventFiles(event);
|
||||
if (isFileTransfer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {target, dataTransfer, pageY} = event;
|
||||
if (!isHTMLElement(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || '';
|
||||
const draggedNode = $getNodeByKey(dragData);
|
||||
if (!draggedNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(event.pageX, pageY),
|
||||
);
|
||||
if (!targetBlockElem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
|
||||
if (!targetNode) {
|
||||
return false;
|
||||
}
|
||||
if (targetNode === draggedNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
insertDraggedNode(draggedNode, targetNode, targetBlockElem, event.pageY);
|
||||
|
||||
setDraggableBlockElem(null);
|
||||
|
||||
return true;
|
||||
@@ -363,7 +400,7 @@ function useDraggableBlockMenu(
|
||||
COMMAND_PRIORITY_HIGH,
|
||||
),
|
||||
);
|
||||
}, [anchorElem, editor]);
|
||||
}, [anchorElem, editor, insertDraggedNode]);
|
||||
|
||||
function onDragStart(event: ReactDragEvent<HTMLDivElement>): void {
|
||||
const dataTransfer = event.dataTransfer;
|
||||
@@ -385,6 +422,79 @@ function useDraggableBlockMenu(
|
||||
hideTargetLine(targetLineRef.current);
|
||||
}
|
||||
|
||||
function onTouchStart(): void {
|
||||
if (!draggableBlockElem) {
|
||||
return;
|
||||
}
|
||||
editor.update(() => {
|
||||
const node = $getNearestNodeFromDOMNode(draggableBlockElem);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const nodeKey = node.getKey();
|
||||
dragDataRef.current = nodeKey;
|
||||
});
|
||||
}
|
||||
|
||||
function onTouchMove(event: TouchEvent) {
|
||||
const {pageX, pageY} = event.targetTouches[0];
|
||||
const rootElement = editor.getRootElement();
|
||||
if (rootElement) {
|
||||
const {top, bottom} = rootElement.getBoundingClientRect();
|
||||
const scrollOffset = 20;
|
||||
if (pageY - top < scrollOffset) {
|
||||
rootElement.scrollTop -= scrollOffset;
|
||||
} else if (bottom - pageY < scrollOffset) {
|
||||
rootElement.scrollTop += scrollOffset;
|
||||
}
|
||||
}
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(pageX, pageY),
|
||||
);
|
||||
const targetLineElem = targetLineRef.current;
|
||||
if (targetBlockElem === null || targetLineElem === null) {
|
||||
return;
|
||||
}
|
||||
setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
|
||||
}
|
||||
|
||||
function onTouchEnd(event: TouchEvent): void {
|
||||
hideTargetLine(targetLineRef.current);
|
||||
|
||||
editor.update(() => {
|
||||
const {pageX, pageY} = event.changedTouches[0];
|
||||
|
||||
const dragData = dragDataRef.current || '';
|
||||
const draggedNode = $getNodeByKey(dragData);
|
||||
if (!draggedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetBlockElem = getBlockElement(
|
||||
anchorElem,
|
||||
editor,
|
||||
new Point(pageX, pageY),
|
||||
);
|
||||
if (!targetBlockElem) {
|
||||
return;
|
||||
}
|
||||
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
|
||||
|
||||
if (!targetNode) {
|
||||
return;
|
||||
}
|
||||
if (targetNode === draggedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
insertDraggedNode(draggedNode, targetNode, targetBlockElem, pageY);
|
||||
});
|
||||
|
||||
setDraggableBlockElem(null);
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<div
|
||||
@@ -392,7 +502,10 @@ function useDraggableBlockMenu(
|
||||
ref={menuRef}
|
||||
draggable={true}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}>
|
||||
onDragEnd={onDragEnd}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}>
|
||||
<div className={isEditable ? 'icon' : ''}>
|
||||
<BlockIcon className="text-text pointer-events-none" />
|
||||
</div>
|
||||
|
||||
@@ -30,12 +30,10 @@
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Lexical__h3 {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.Lexical__textBold {
|
||||
font-weight: bold;
|
||||
@@ -325,6 +323,7 @@
|
||||
}
|
||||
.Lexical__listItemChecked {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.Lexical__listItemUnchecked:before,
|
||||
.Lexical__listItemChecked:before {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Editor/BlocksEditor';
|
||||
export * from './Editor/BlocksEditorComposer';
|
||||
export * from './Editor/Constants';
|
||||
export * from './Editor/MarkdownTransformers';
|
||||
|
||||
@@ -3,6 +3,116 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.104.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.104.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [3.103.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.103.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.103.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
### Features
|
||||
|
||||
* screen presentation and dismiss animations for mobile ([#2073](https://github.com/standardnotes/app/issues/2073)) ([7f2074a](https://github.com/standardnotes/app/commit/7f2074a6ec59068f75dae76d31721d65e61c9673))
|
||||
|
||||
## [3.102.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.102.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.102.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [3.101.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.101.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.101.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,10 @@
|
||||
import { FileBackupRecord, FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import {
|
||||
FileBackupRecord,
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { AppState } from 'app/AppState'
|
||||
import { shell } from 'electron'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
@@ -6,13 +12,14 @@ import path from 'path'
|
||||
import {
|
||||
deleteFile,
|
||||
ensureDirectoryExists,
|
||||
moveFiles,
|
||||
moveDirContents,
|
||||
openDirectoryPicker,
|
||||
readJSONFile,
|
||||
writeFile,
|
||||
writeJSONFile,
|
||||
} from '../Utils/FileUtils'
|
||||
import { FileDownloader } from './FileDownloader'
|
||||
import { FileReadOperation } from './FileReadOperation'
|
||||
|
||||
export const FileBackupsConstantsV1 = {
|
||||
Version: '1.0.0',
|
||||
@@ -21,6 +28,8 @@ export const FileBackupsConstantsV1 = {
|
||||
}
|
||||
|
||||
export class FilesBackupManager implements FileBackupsDevice {
|
||||
private readOperations: Map<string, FileReadOperation> = new Map()
|
||||
|
||||
constructor(private appState: AppState) {}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
@@ -78,8 +87,11 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
}
|
||||
|
||||
const entries = Object.values(mapping.files)
|
||||
const itemFolders = entries.map((entry) => path.join(oldPath, entry.relativePath))
|
||||
await moveFiles(itemFolders, newPath)
|
||||
for (const entry of entries) {
|
||||
const sourcePath = path.join(oldPath, entry.relativePath)
|
||||
const destinationPath = path.join(newPath, entry.relativePath)
|
||||
await moveDirContents(sourcePath, destinationPath)
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
entry.absolutePath = path.join(newPath, entry.relativePath)
|
||||
@@ -188,4 +200,28 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
const operation = new FileReadOperation(record)
|
||||
|
||||
this.readOperations.set(operation.token, operation)
|
||||
|
||||
return operation.token
|
||||
}
|
||||
|
||||
async readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
|
||||
const operation = this.readOperations.get(token)
|
||||
|
||||
if (!operation) {
|
||||
return Promise.reject(new Error('Invalid token'))
|
||||
}
|
||||
|
||||
const result = await operation.readNextChunk()
|
||||
|
||||
if (result.isLast) {
|
||||
this.readOperations.delete(token)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { FileBackupReadChunkResponse, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const ONE_MB = 1024 * 1024
|
||||
const CHUNK_LIMIT = ONE_MB * 5
|
||||
|
||||
export class FileReadOperation {
|
||||
public readonly token: string
|
||||
private currentChunkLocation = 0
|
||||
private localFileId: number
|
||||
private fileLength: number
|
||||
|
||||
constructor(backupRecord: FileBackupRecord) {
|
||||
this.token = backupRecord.absolutePath
|
||||
this.localFileId = fs.openSync(path.join(backupRecord.absolutePath, backupRecord.binaryFileName), 'r')
|
||||
this.fileLength = fs.fstatSync(this.localFileId).size
|
||||
}
|
||||
|
||||
async readNextChunk(): Promise<FileBackupReadChunkResponse> {
|
||||
let isLast = false
|
||||
let readUpto = this.currentChunkLocation + CHUNK_LIMIT
|
||||
if (readUpto > this.fileLength) {
|
||||
readUpto = this.fileLength
|
||||
isLast = true
|
||||
}
|
||||
|
||||
const readLength = readUpto - this.currentChunkLocation
|
||||
|
||||
const chunk = await this.readChunk(this.currentChunkLocation, readLength)
|
||||
|
||||
this.currentChunkLocation = readUpto
|
||||
|
||||
if (isLast) {
|
||||
fs.close(this.localFileId)
|
||||
}
|
||||
|
||||
return {
|
||||
chunk,
|
||||
isLast,
|
||||
progress: {
|
||||
encryptedFileSize: this.fileLength,
|
||||
encryptedBytesDownloaded: this.currentChunkLocation,
|
||||
encryptedBytesRemaining: this.fileLength - this.currentChunkLocation,
|
||||
percentComplete: (this.currentChunkLocation / this.fileLength) * 100.0,
|
||||
source: 'local',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async readChunk(start: number, length: number): Promise<Uint8Array> {
|
||||
const buffer = Buffer.alloc(length)
|
||||
|
||||
fs.readSync(this.localFileId, buffer, 0, length, start)
|
||||
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,13 @@ import { StoreKeys } from '../Store/StoreKeys'
|
||||
const path = require('path')
|
||||
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
|
||||
import { FileBackupsDevice, FileBackupsMapping, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import {
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import { BackupsManagerInterface } from '../Backups/BackupsManagerInterface'
|
||||
import { KeychainInterface } from '../Keychain/KeychainInterface'
|
||||
@@ -65,6 +71,8 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
getFilesBackupsLocation: this.getFilesBackupsLocation.bind(this),
|
||||
openFilesBackupsLocation: this.openFilesBackupsLocation.bind(this),
|
||||
openFileBackup: this.openFileBackup.bind(this),
|
||||
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
|
||||
readNextChunk: this.readNextChunk.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +188,14 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
return this.fileBackups.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.fileBackups.getFileBackupReadToken(record)
|
||||
}
|
||||
|
||||
readNextChunk(nextToken: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.fileBackups.readNextChunk(nextToken)
|
||||
}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.fileBackups.isFilesBackupsEnabled()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { removeFromArray } from '../Utils/Utils'
|
||||
|
||||
export const FileDoesNotExist = 'ENOENT'
|
||||
export const FileAlreadyExists = 'EEXIST'
|
||||
const CrossDeviceLink = 'EXDEV'
|
||||
const OperationNotPermitted = 'EPERM'
|
||||
const DeviceIsBusy = 'EBUSY'
|
||||
|
||||
@@ -152,8 +151,14 @@ function isChildOfDir(parent: string, potentialChild: string) {
|
||||
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
|
||||
}
|
||||
|
||||
export async function moveDirContents(srcDir: string, destDir: string): Promise<void[]> {
|
||||
let fileNames = await fs.promises.readdir(srcDir)
|
||||
export async function moveDirContents(srcDir: string, destDir: string): Promise<void> {
|
||||
let fileNames: string[]
|
||||
try {
|
||||
fileNames = await fs.promises.readdir(srcDir)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
await ensureDirectoryExists(destDir)
|
||||
|
||||
if (isChildOfDir(srcDir, destDir)) {
|
||||
@@ -163,10 +168,14 @@ export async function moveDirContents(srcDir: string, destDir: string): Promise<
|
||||
removeFromArray(fileNames, path.basename(destDir))
|
||||
}
|
||||
|
||||
return moveFiles(
|
||||
fileNames.map((fileName) => path.join(srcDir, fileName)),
|
||||
destDir,
|
||||
)
|
||||
try {
|
||||
await moveFiles(
|
||||
fileNames.map((fileName) => path.join(srcDir, fileName)),
|
||||
destDir,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractZip(source: string, dest: string): Promise<void> {
|
||||
@@ -245,14 +254,10 @@ export async function moveFiles(sources: string[], destDir: string): Promise<voi
|
||||
async function moveFile(source: PathLike, destination: PathLike) {
|
||||
try {
|
||||
await fs.promises.rename(source, destination)
|
||||
} catch (error: any) {
|
||||
if (error.code === CrossDeviceLink) {
|
||||
/** Fall back to copying and then deleting. */
|
||||
await fs.promises.copyFile(source, destination)
|
||||
await fs.promises.unlink(source)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
} catch (_error) {
|
||||
/** Fall back to copying and then deleting. */
|
||||
await fs.promises.copyFile(source, destination, fs.constants.COPYFILE_FICLONE_FORCE)
|
||||
await fs.promises.unlink(source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
FileBackupsMapping,
|
||||
RawKeychainValue,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
|
||||
import { Component } from '../Main/Packages/PackageManagerInterface'
|
||||
@@ -149,6 +151,14 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
return this.remoteBridge.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.remoteBridge.getFileBackupReadToken(record)
|
||||
}
|
||||
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.remoteBridge.readNextChunk(token)
|
||||
}
|
||||
|
||||
async performHardReset(): Promise<void> {
|
||||
console.error('performHardReset is not yet implemented')
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ declare global {
|
||||
plansUrl: string
|
||||
purchaseUrl: string
|
||||
startApplication: StartApplication
|
||||
zip: any
|
||||
zip: unknown
|
||||
electronMainEvents: any
|
||||
}
|
||||
}
|
||||
@@ -88,15 +88,15 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
/*
|
||||
Title bar events
|
||||
*/
|
||||
document.getElementById('menu-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('menu-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.displayAppMenu()
|
||||
})
|
||||
|
||||
document.getElementById('min-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('min-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.minimizeWindow()
|
||||
})
|
||||
|
||||
document.getElementById('max-btn')!.addEventListener('click', async () => {
|
||||
document.getElementById('max-btn')?.addEventListener('click', async () => {
|
||||
if (remoteBridge.isWindowMaximized()) {
|
||||
remoteBridge.unmaximizeWindow()
|
||||
} else {
|
||||
@@ -104,15 +104,12 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('close-btn')!.addEventListener('click', () => {
|
||||
document.getElementById('close-btn')?.addEventListener('click', () => {
|
||||
remoteBridge.closeWindow()
|
||||
})
|
||||
|
||||
// For Mac inset window
|
||||
const sheet = document.styleSheets[0]
|
||||
if (isMacOS) {
|
||||
sheet.insertRule('#navigation-content { padding-top: 25px !important; }', sheet.cssRules.length)
|
||||
}
|
||||
|
||||
if (isMacOS || useSystemMenuBar) {
|
||||
// !important is important here because #desktop-title-bar has display: flex.
|
||||
@@ -141,7 +138,7 @@ window.electronMainEvents.handlePerformAutomatedBackup(() => {
|
||||
void window.device.downloadBackup()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: any) => {
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: { success: boolean }) => {
|
||||
window.webClient.didFinishBackup(data.success)
|
||||
})
|
||||
|
||||
|
||||
@@ -10,18 +10,14 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.mac-desktop .app-column.selected {
|
||||
padding-top: 18px;
|
||||
.mac-desktop .app-pane {
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.mac-desktop #app.collapsed-notes.collapsed-navigation #editor-column {
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.mac-desktop #app.collapsed-navigation #items-column {
|
||||
padding-top: 18px;
|
||||
.mac-desktop .app-pane-1 {
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.101.0",
|
||||
"version": "3.104.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -31,7 +31,7 @@ async function getBlockMapInfo(fileName) {
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const { version } = JSON.parse(await fs.promises.readFile('package.json'))
|
||||
const version = process.argv.slice(2)[0]
|
||||
const zipName = `standard-notes-${version}-mac-x64.zip`
|
||||
const zipPath = `dist/${zipName}`
|
||||
console.log(`Removing ${zipPath}`)
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.19.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.17",
|
||||
"version": "1.19.24",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.27.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [1.26.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.26.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.26.0",
|
||||
"version": "1.27.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ByteChunker, OnChunkCallback } from '@standardnotes/files'
|
||||
import { ByteChunker, OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
import { FileSelectionResponse } from '../types'
|
||||
import { readFile as utilsReadFile } from '../utils'
|
||||
import { FileReaderInterface } from '../Interface/FileReader'
|
||||
@@ -39,7 +39,7 @@ function selectFiles(): Promise<File[]> {
|
||||
async function readFile(
|
||||
file: File,
|
||||
minimumChunkSize: number,
|
||||
onChunk: OnChunkCallback,
|
||||
onChunk: OnChunkCallbackNoProgress,
|
||||
): Promise<FileSelectionResponse> {
|
||||
const buffer = await utilsReadFile(file)
|
||||
const chunker = new ByteChunker(minimumChunkSize, onChunk)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { OnChunkCallback } from '@standardnotes/files'
|
||||
import { OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
|
||||
import { FileSelectionResponse } from '../types'
|
||||
|
||||
export interface FileReaderInterface {
|
||||
selectFiles(): Promise<File[]>
|
||||
|
||||
readFile(file: File, minimumChunkSize: number, onChunk: OnChunkCallback): Promise<FileSelectionResponse>
|
||||
readFile(file: File, minimumChunkSize: number, onChunk: OnChunkCallbackNoProgress): Promise<FileSelectionResponse>
|
||||
|
||||
available(): boolean
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ByteChunker, OnChunkCallback } from '@standardnotes/files'
|
||||
import { ByteChunker, OnChunkCallbackNoProgress } from '@standardnotes/files'
|
||||
import { FileReaderInterface } from './../Interface/FileReader'
|
||||
import { FileSelectionResponse } from '../types'
|
||||
|
||||
@@ -39,7 +39,7 @@ async function selectFiles(): Promise<File[]> {
|
||||
async function readFile(
|
||||
file: File,
|
||||
minimumChunkSize: number,
|
||||
onChunk: OnChunkCallback,
|
||||
onChunk: OnChunkCallbackNoProgress,
|
||||
): Promise<FileSelectionResponse> {
|
||||
const byteChunker = new ByteChunker(minimumChunkSize, onChunk)
|
||||
const stream = file.stream() as unknown as ReadableStream
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [1.13.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.13.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('byte chunker', () => {
|
||||
it('should hold back small chunks until minimum size is met', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), false)
|
||||
@@ -25,9 +25,9 @@ describe('byte chunker', () => {
|
||||
it('should send back big chunks immediately', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(150), false)
|
||||
@@ -42,9 +42,9 @@ describe('byte chunker', () => {
|
||||
it('last chunk should be popped regardless of size', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), false)
|
||||
@@ -57,9 +57,9 @@ describe('byte chunker', () => {
|
||||
it('single chunk should be popped immediately', async () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numChunks = 0
|
||||
const chunker = new ByteChunker(100, async (bytes) => {
|
||||
const chunker = new ByteChunker(100, async (chunk) => {
|
||||
numChunks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(50), true)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { OnChunkCallback } from './OnChunkCallback'
|
||||
import { OnChunkCallbackNoProgress } from './OnChunkCallback'
|
||||
|
||||
export class ByteChunker {
|
||||
public loggingEnabled = false
|
||||
private bytes = new Uint8Array()
|
||||
private index = 1
|
||||
|
||||
constructor(private minimumChunkSize: number, private onChunk: OnChunkCallback) {}
|
||||
constructor(private minimumChunkSize: number, private onChunk: OnChunkCallbackNoProgress) {}
|
||||
|
||||
private log(...args: any[]): void {
|
||||
if (!this.loggingEnabled) {
|
||||
@@ -27,9 +27,13 @@ export class ByteChunker {
|
||||
|
||||
private async popBytes(isLast: boolean): Promise<void> {
|
||||
const maxIndex = Math.max(this.minimumChunkSize, this.bytes.length)
|
||||
|
||||
const chunk = this.bytes.slice(0, maxIndex)
|
||||
|
||||
this.bytes = new Uint8Array([...this.bytes.slice(maxIndex)])
|
||||
|
||||
this.log(`Chunker popping ${chunk.length}, total size in queue ${this.bytes.length}`)
|
||||
await this.onChunk(chunk, this.index++, isLast)
|
||||
|
||||
await this.onChunk({ data: chunk, index: this.index++, isLast })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
export type OnChunkCallback = (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
|
||||
export type OnChunkCallback = (chunk: {
|
||||
data: Uint8Array
|
||||
index: number
|
||||
isLast: boolean
|
||||
progress: FileDownloadProgress
|
||||
}) => Promise<void>
|
||||
|
||||
export type OnChunkCallbackNoProgress = (chunk: { data: Uint8Array; index: number; isLast: boolean }) => Promise<void>
|
||||
|
||||
@@ -10,9 +10,30 @@ describe('ordered byte chunker', () => {
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numCallbacks = 0
|
||||
|
||||
const chunker = new OrderedByteChunker(chunkSizes, async (bytes) => {
|
||||
const chunker = new OrderedByteChunker(chunkSizes, 'network', async (chunk) => {
|
||||
numCallbacks++
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(30))
|
||||
|
||||
expect(numCallbacks).toEqual(3)
|
||||
expect(receivedBytes.length).toEqual(30)
|
||||
})
|
||||
|
||||
it('should correctly report progress', async () => {
|
||||
const chunkSizes = [10, 10, 10]
|
||||
let receivedBytes = new Uint8Array()
|
||||
let numCallbacks = 0
|
||||
|
||||
const chunker = new OrderedByteChunker(chunkSizes, 'network', async (chunk) => {
|
||||
numCallbacks++
|
||||
|
||||
receivedBytes = new Uint8Array([...receivedBytes, ...chunk.data])
|
||||
|
||||
expect(chunk.progress.encryptedBytesDownloaded).toEqual(receivedBytes.length)
|
||||
|
||||
expect(chunk.progress.percentComplete).toEqual((numCallbacks / chunkSizes.length) * 100.0)
|
||||
})
|
||||
|
||||
await chunker.addBytes(chunkOfSize(30))
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
import { OnChunkCallback } from './OnChunkCallback'
|
||||
|
||||
export class OrderedByteChunker {
|
||||
private bytes = new Uint8Array()
|
||||
private index = 1
|
||||
private remainingChunks: number[] = []
|
||||
private fileSize: number
|
||||
|
||||
constructor(
|
||||
private chunkSizes: number[],
|
||||
private onChunk: (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>,
|
||||
private source: FileDownloadProgress['source'],
|
||||
private onChunk: OnChunkCallback,
|
||||
) {
|
||||
this.remainingChunks = chunkSizes.slice()
|
||||
|
||||
this.fileSize = chunkSizes.reduce((acc, size) => acc + size, 0)
|
||||
}
|
||||
|
||||
private get bytesPopped(): number {
|
||||
return this.fileSize - this.bytesRemaining
|
||||
}
|
||||
|
||||
private get bytesRemaining(): number {
|
||||
return this.remainingChunks.reduce((acc, size) => acc + size, 0)
|
||||
}
|
||||
|
||||
private needsPop(): boolean {
|
||||
@@ -31,7 +46,18 @@ export class OrderedByteChunker {
|
||||
|
||||
this.remainingChunks.shift()
|
||||
|
||||
await this.onChunk(chunk, this.index++, this.index === this.chunkSizes.length - 1)
|
||||
await this.onChunk({
|
||||
data: chunk,
|
||||
index: this.index++,
|
||||
isLast: this.index === this.chunkSizes.length - 1,
|
||||
progress: {
|
||||
encryptedFileSize: this.fileSize,
|
||||
encryptedBytesDownloaded: this.bytesPopped,
|
||||
encryptedBytesRemaining: this.bytesRemaining,
|
||||
percentComplete: (this.bytesPopped / this.fileSize) * 100.0,
|
||||
source: this.source,
|
||||
},
|
||||
})
|
||||
|
||||
if (this.needsPop()) {
|
||||
await this.popBytes()
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
import { FileBackupRecord, FileBackupsMapping } from './FileBackupsMapping'
|
||||
|
||||
export type FileBackupReadToken = string
|
||||
export type FileBackupReadChunkResponse = { chunk: Uint8Array; isLast: boolean; progress: FileDownloadProgress }
|
||||
|
||||
export interface FileBackupsDevice {
|
||||
getFilesBackupsMappingFile(): Promise<FileBackupsMapping>
|
||||
saveFilesBackupsFile(
|
||||
@@ -12,6 +16,8 @@ export interface FileBackupsDevice {
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'>
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken>
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { logWithColor } from '@standardnotes/utils'
|
||||
|
||||
declare const process: {
|
||||
env: {
|
||||
NODE_ENV: string | null | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'
|
||||
|
||||
export enum LoggingDomain {
|
||||
FilesPackage,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.FilesPackage]: false,
|
||||
}
|
||||
|
||||
const DomainColor: Record<LoggingDomain, string> = {
|
||||
[LoggingDomain.FilesPackage]: 'green',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDev || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
logWithColor(LoggingDomain[domain], DomainColor[domain], ...args)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
|
||||
import { FileBackupRecord } from '../Device/FileBackupsMapping'
|
||||
|
||||
export interface BackupServiceInterface {
|
||||
getFileBackupInfo(file: { uuid: string }): Promise<FileBackupRecord | undefined>
|
||||
readEncryptedFileFromBackup(uuid: string, onChunk: OnChunkCallback): Promise<'success' | 'failed' | 'aborted'>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
changeFilesBackupsLocation(): Promise<string | undefined>
|
||||
getFilesBackupsLocation(): Promise<string>
|
||||
openFilesBackupsLocation(): Promise<void>
|
||||
openFileBackup(record: FileBackupRecord): Promise<void>
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export interface FilesClientInterface {
|
||||
|
||||
downloadFile(
|
||||
file: FileItem,
|
||||
onDecryptedBytes: (bytes: Uint8Array, progress: FileDownloadProgress | undefined) => Promise<void>,
|
||||
onDecryptedBytes: (bytes: Uint8Array, progress: FileDownloadProgress) => Promise<void>,
|
||||
): Promise<ClientDisplayableError | undefined>
|
||||
|
||||
deleteFile(file: FileItem): Promise<ClientDisplayableError | undefined>
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import { FileSystemApi } from '../Api/FileSystemApi'
|
||||
import { FileHandleRead } from '../Api/FileHandleRead'
|
||||
import { OrderedByteChunker } from '../Chunker/OrderedByteChunker'
|
||||
|
||||
export async function readAndDecryptBackupFile(
|
||||
export async function readAndDecryptBackupFileUsingFileSystemAPI(
|
||||
fileHandle: FileHandleRead,
|
||||
file: {
|
||||
encryptionHeader: FileContent['encryptionHeader']
|
||||
@@ -19,8 +19,8 @@ export async function readAndDecryptBackupFile(
|
||||
): Promise<'aborted' | 'failed' | 'success'> {
|
||||
const decryptor = new FileDecryptor(file, crypto)
|
||||
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, async (chunk: Uint8Array) => {
|
||||
const decryptResult = decryptor.decryptBytes(chunk)
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, 'local', async (chunk) => {
|
||||
const decryptResult = decryptor.decryptBytes(chunk.data)
|
||||
|
||||
if (!decryptResult) {
|
||||
return
|
||||
@@ -0,0 +1,52 @@
|
||||
import { FileContent } from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { FileDecryptor } from '../UseCase/FileDecryptor'
|
||||
import { OrderedByteChunker } from '../Chunker/OrderedByteChunker'
|
||||
import { BackupServiceInterface } from './BackupServiceInterface'
|
||||
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
|
||||
import { log, LoggingDomain } from '../Logging'
|
||||
|
||||
export async function readAndDecryptBackupFileUsingBackupService(
|
||||
file: {
|
||||
uuid: string
|
||||
encryptionHeader: FileContent['encryptionHeader']
|
||||
remoteIdentifier: FileContent['remoteIdentifier']
|
||||
encryptedChunkSizes: FileContent['encryptedChunkSizes']
|
||||
key: FileContent['key']
|
||||
},
|
||||
backupService: BackupServiceInterface,
|
||||
crypto: PureCryptoInterface,
|
||||
onDecryptedBytes: OnChunkCallback,
|
||||
): Promise<'aborted' | 'failed' | 'success'> {
|
||||
log(
|
||||
LoggingDomain.FilesPackage,
|
||||
'Reading and decrypting backup file',
|
||||
file.uuid,
|
||||
'chunk sizes',
|
||||
file.encryptedChunkSizes,
|
||||
)
|
||||
|
||||
const decryptor = new FileDecryptor(file, crypto)
|
||||
|
||||
const byteChunker = new OrderedByteChunker(file.encryptedChunkSizes, 'local', async (chunk) => {
|
||||
log(LoggingDomain.FilesPackage, 'OrderedByteChunker did pop bytes', chunk.data.length, chunk.progress)
|
||||
|
||||
const decryptResult = decryptor.decryptBytes(chunk.data)
|
||||
|
||||
if (!decryptResult) {
|
||||
return
|
||||
}
|
||||
|
||||
await onDecryptedBytes({ ...chunk, data: decryptResult.decryptedBytes })
|
||||
})
|
||||
|
||||
const readResult = await backupService.readEncryptedFileFromBackup(file.uuid, async (chunk) => {
|
||||
log(LoggingDomain.FilesPackage, 'Got file chunk from backup service', chunk.data.length, chunk.progress)
|
||||
|
||||
await byteChunker.addBytes(chunk.data)
|
||||
})
|
||||
|
||||
log(LoggingDomain.FilesPackage, 'Finished reading and decrypting backup file', file.uuid)
|
||||
|
||||
return readResult
|
||||
}
|
||||
@@ -3,4 +3,24 @@ export type FileDownloadProgress = {
|
||||
encryptedBytesDownloaded: number
|
||||
encryptedBytesRemaining: number
|
||||
percentComplete: number
|
||||
source: 'network' | 'local' | 'memcache'
|
||||
}
|
||||
|
||||
export function fileProgressToHumanReadableString(
|
||||
progress: FileDownloadProgress,
|
||||
fileName: string,
|
||||
options: { showPercent: boolean },
|
||||
): string {
|
||||
const progressPercent = Math.floor(progress.percentComplete)
|
||||
|
||||
const sourceString =
|
||||
progress.source === 'network' ? '' : progress.source === 'memcache' ? 'from cache' : 'from backup'
|
||||
|
||||
let result = `Downloading file ${sourceString} "${fileName}"`
|
||||
|
||||
if (options.showPercent) {
|
||||
result += ` (${progressPercent}%)`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export class FileDownloader {
|
||||
encryptedBytesDownloaded: this.totalBytesDownloaded,
|
||||
encryptedBytesRemaining: encryptedSize - this.totalBytesDownloaded,
|
||||
percentComplete: (this.totalBytesDownloaded / encryptedSize) * 100.0,
|
||||
source: 'network',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ export * from './Device/FileBackupMetadataFile'
|
||||
export * from './Device/FileBackupsConstantsV1'
|
||||
export * from './Device/FileBackupsDevice'
|
||||
export * from './Device/FileBackupsMapping'
|
||||
export * from './Service/BackupServiceInterface'
|
||||
export * from './Service/FilesClientInterface'
|
||||
export * from './Service/ReadAndDecryptBackupFile'
|
||||
export * from './Service/ReadAndDecryptBackupFileFileSystemAPI'
|
||||
export * from './Service/ReadAndDecryptBackupFileUsingBackupService'
|
||||
export * from './Operations/DownloadAndDecrypt'
|
||||
export * from './Operations/EncryptAndUpload'
|
||||
export * from './UseCase/FileDecryptor'
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add new line after embedding files ([7f6c53d](https://github.com/standardnotes/app/commit/7f6c53dfcbc8952ca9eee76b9c4af70fea3c1bf1))
|
||||
|
||||
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/icons",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.99992 15C8.67384 15 7.40207 14.4732 6.46438 13.5355C5.5267 12.5978 4.99992 11.3261 4.99992 9.99998C4.99992 9.16665 5.20825 8.35831 5.58325 7.66665L4.36659 6.44998C3.71659 7.47498 3.33325 8.69165 3.33325 9.99998C3.33325 11.7681 4.03563 13.4638 5.28587 14.714C6.53612 15.9643 8.23181 16.6666 9.99992 16.6666V19.1666L13.3333 15.8333L9.99992 12.5V15ZM9.99992 3.33331V0.833313L6.66658 4.16665L9.99992 7.49998V4.99998C11.326 4.99998 12.5978 5.52676 13.5355 6.46445C14.4731 7.40213 14.9999 8.6739 14.9999 9.99998C14.9999 10.8333 14.7916 11.6416 14.4166 12.3333L15.6333 13.55C16.2833 12.525 16.6666 11.3083 16.6666 9.99998C16.6666 8.23187 15.9642 6.53618 14.714 5.28593C13.4637 4.03569 11.768 3.33331 9.99992 3.33331Z" />
|
||||
<path d="M9.99999 15C11.3261 15 12.5978 14.4732 13.5355 13.5355C14.4732 12.5979 15 11.3261 15 10C15 9.16666 14.7917 8.35833 14.4167 7.66667L15.6333 6.45C16.2833 7.475 16.6667 8.69166 16.6667 10C16.6667 11.7681 15.9643 13.4638 14.714 14.714C13.4638 15.9643 11.7681 16.6667 9.99999 16.6667V19.1667L6.66666 15.8333L9.99999 12.5V15ZM9.99999 3.33333V0.833333L13.3333 4.16667L9.99999 7.5V5C8.67391 5 7.40214 5.52678 6.46446 6.46447C5.52677 7.40215 4.99999 8.67392 4.99999 10C4.99999 10.8333 5.20832 11.6417 5.58332 12.3333L4.36666 13.55C3.71666 12.525 3.33332 11.3083 3.33332 10C3.33332 8.23189 4.0357 6.5362 5.28594 5.28595C6.53619 4.03571 8.23188 3.33333 9.99999 3.33333Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 756 B |
@@ -3,6 +3,118 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.49.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
## [3.48.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.48.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.48.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* display number of files for 'Files' view ([#2065](https://github.com/standardnotes/app/issues/2065)) ([767d354](https://github.com/standardnotes/app/commit/767d3547804bc98693dcc769baeed943c24426f3))
|
||||
|
||||
## [3.47.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Super editor popover menus on iOS ([#2061](https://github.com/standardnotes/app/issues/2061)) ([148aee4](https://github.com/standardnotes/app/commit/148aee441b816c58599d95db2e5b0f3f93414511))
|
||||
|
||||
## [3.47.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.47.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.47.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [3.46.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.46.29",
|
||||
"version": "3.49.0",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Keyboard, Platform } from 'react-native'
|
||||
import VersionInfo from 'react-native-version-info'
|
||||
import { WebView, WebViewMessageEvent } from 'react-native-webview'
|
||||
import { OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes'
|
||||
import pjson from '../package.json'
|
||||
import { AndroidBackHandlerService } from './AndroidBackHandlerService'
|
||||
import { AppStateObserverService } from './AppStateObserverService'
|
||||
import { ColorSchemeObserverService } from './ColorSchemeObserverService'
|
||||
@@ -129,7 +128,7 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
const WebProcessDeviceInterface = `
|
||||
class WebProcessDeviceInterface {
|
||||
constructor(messageSender) {
|
||||
this.appVersion = '${pjson.version} (${VersionInfo.buildVersion})'
|
||||
this.appVersion = '${VersionInfo.appVersion} (${VersionInfo.buildVersion})'
|
||||
this.environment = 3
|
||||
this.platform = ${device.platform}
|
||||
this.databases = []
|
||||
@@ -254,6 +253,9 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
return true
|
||||
}
|
||||
|
||||
const requireInlineMediaPlaybackForMomentsFeature = true
|
||||
const requireMediaUserInteractionForMomentsFeature = false
|
||||
|
||||
return (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
@@ -275,6 +277,8 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
injectedJavaScriptBeforeContentLoaded={injectedJS}
|
||||
bounces={false}
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
allowsInlineMediaPlayback={requireInlineMediaPlaybackForMomentsFeature}
|
||||
mediaPlaybackRequiresUserAction={requireMediaUserInteractionForMomentsFeature}
|
||||
scalesPageToFit={true}
|
||||
/**
|
||||
* This disables the global window scroll but keeps scroll within div elements like lists and textareas.
|
||||
|
||||
@@ -3,6 +3,40 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.39.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
## [1.38.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.38.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
# [1.38.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* display number of files for 'Files' view ([#2065](https://github.com/standardnotes/app/issues/2065)) ([767d354](https://github.com/standardnotes/app/commit/767d3547804bc98693dcc769baeed943c24426f3))
|
||||
|
||||
## [1.37.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exclude files from being counted in 'Notes' view ([#2062](https://github.com/standardnotes/app/issues/2062)) ([2b0c9b1](https://github.com/standardnotes/app/commit/2b0c9b188cd75477c0c1611443daadb8ffa29306))
|
||||
|
||||
## [1.37.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.37.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.37.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.37.6",
|
||||
"version": "1.39.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export type TagItemCountChangeObserver = (tagUuid: Uuid | AllNotesUuidSignifier)
|
||||
export class TagItemsIndex implements SNIndex {
|
||||
private tagToItemsMap: Partial<Record<Uuid, Set<Uuid>>> = {}
|
||||
private allCountableItems = new Set<Uuid>()
|
||||
private countableItemsByType = new Map<ContentType, Set<Uuid>>()
|
||||
|
||||
constructor(private collection: ItemCollection, public observers: TagItemCountChangeObserver[] = []) {}
|
||||
|
||||
@@ -41,6 +42,14 @@ export class TagItemsIndex implements SNIndex {
|
||||
return this.allCountableItems.size
|
||||
}
|
||||
|
||||
public allCountableNotesCount(): number {
|
||||
return this.countableItemsByType.get(ContentType.Note)?.size || 0
|
||||
}
|
||||
|
||||
public allCountableFilesCount(): number {
|
||||
return this.countableItemsByType.get(ContentType.File)?.size || 0
|
||||
}
|
||||
|
||||
public countableItemsForTag(tag: SNTag): number {
|
||||
return this.tagToItemsMap[tag.uuid]?.size || 0
|
||||
}
|
||||
@@ -75,10 +84,18 @@ export class TagItemsIndex implements SNIndex {
|
||||
|
||||
for (const item of items) {
|
||||
const isCountable = this.isItemCountable(item)
|
||||
|
||||
if (isCountable) {
|
||||
this.allCountableItems.add(item.uuid)
|
||||
|
||||
if (!this.countableItemsByType.has(item.content_type)) {
|
||||
this.countableItemsByType.set(item.content_type, new Set())
|
||||
}
|
||||
|
||||
this.countableItemsByType.get(item.content_type)?.add(item.uuid)
|
||||
} else {
|
||||
this.allCountableItems.delete(item.uuid)
|
||||
this.countableItemsByType.get(item.content_type)?.delete(item.uuid)
|
||||
}
|
||||
|
||||
const associatedTagUuids = this.collection.uuidsThatReferenceUuid(item.uuid)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { dateFromDSLDateString } from './Utils'
|
||||
import { addDaysToDate, addHoursToDate } from '@standardnotes/utils'
|
||||
|
||||
describe('Predicate Utils', () => {
|
||||
describe('dateFromDSLDateString', () => {
|
||||
it('should return a date object with the correct day', () => {
|
||||
const date = dateFromDSLDateString('1.days.ago')
|
||||
expect(date.getDate()).toEqual(new Date().getDate() - 1)
|
||||
expect(date.getDate()).toEqual(addDaysToDate(new Date(), -1).getDate())
|
||||
})
|
||||
|
||||
it('should return a date object with the correct hour', () => {
|
||||
const date = dateFromDSLDateString('1.hours.ago')
|
||||
expect(date.getHours()).toEqual(new Date().getHours() - 1)
|
||||
expect(date.getHours()).toEqual(addHoursToDate(new Date(), -1).getHours())
|
||||
})
|
||||
|
||||
it('should return a date object with the correct month', () => {
|
||||
|
||||
@@ -12,10 +12,7 @@ import { PayloadTimestampDefaults } from '../../Abstract/Payload'
|
||||
import { FilterDisplayOptions } from '../../Runtime/Display'
|
||||
import { FileItem } from '../File'
|
||||
|
||||
export function BuildSmartViews(
|
||||
options: FilterDisplayOptions,
|
||||
{ supportsFileNavigation = false }: { supportsFileNavigation: boolean },
|
||||
): SmartView[] {
|
||||
export function BuildSmartViews(options: FilterDisplayOptions): SmartView[] {
|
||||
const notes = new SmartView(
|
||||
new DecryptedPayload({
|
||||
uuid: SystemViewId.AllNotes,
|
||||
@@ -88,11 +85,7 @@ export function BuildSmartViews(
|
||||
}),
|
||||
)
|
||||
|
||||
if (supportsFileNavigation) {
|
||||
return [notes, starred, files, archived, trash, untagged]
|
||||
} else {
|
||||
return [notes, starred, archived, trash, untagged]
|
||||
}
|
||||
return [notes, files, starred, archived, trash, untagged]
|
||||
}
|
||||
|
||||
function allNotesPredicate(options: FilterDisplayOptions) {
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum PrefKey {
|
||||
UpdateSavingStatusIndicator = 'updateSavingStatusIndicator',
|
||||
DarkMode = 'darkMode',
|
||||
DefaultEditorIdentifier = 'defaultEditorIdentifier',
|
||||
MomentsDefaultTagUuid = 'momentsDefaultTagUuid',
|
||||
}
|
||||
|
||||
export enum NewNoteTitleFormat {
|
||||
@@ -103,4 +104,5 @@ export type PrefValue = {
|
||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||
[PrefKey.DarkMode]: boolean
|
||||
[PrefKey.DefaultEditorIdentifier]: EditorIdentifier
|
||||
[PrefKey.MomentsDefaultTagUuid]: string | undefined
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type VectorIconNameOrEmoji = EmojiString | IconType
|
||||
|
||||
export type EmojiString = string
|
||||
export type EmojiString = Omit<string, IconType>
|
||||
|
||||
export type IconType =
|
||||
| 'accessibility'
|
||||
|
||||
@@ -3,6 +3,116 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
# [1.4.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [1.3.257](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.256](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.255](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.255",
|
||||
"version": "1.4.24",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -5,13 +5,13 @@ import { writeJson, ensureDirExists } from '../../../scripts/ScriptUtils.mjs'
|
||||
|
||||
const CdnUrl = 'https://github.com/standardnotes/app/releases/download/'
|
||||
const DesktopPackageName = '@standardnotes/desktop'
|
||||
const DesktopVersion = Desktop.version
|
||||
const DesktopVersion = Web.version
|
||||
const BaseFileName = `standard-notes-${DesktopVersion}`
|
||||
const ReleaseUrl = `${CdnUrl}${DesktopPackageName}@${DesktopVersion}/${BaseFileName}`.replaceAll('@', '%40')
|
||||
|
||||
const Versions = {
|
||||
[Desktop.name]: Desktop.version,
|
||||
[Mobile.name]: Mobile.version,
|
||||
[Desktop.name]: Web.version,
|
||||
[Mobile.name]: Web.version,
|
||||
[Web.name]: Web.version,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.50.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
# [1.49.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [1.48.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.48.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* display number of files for 'Files' view ([#2065](https://github.com/standardnotes/app/issues/2065)) ([767d354](https://github.com/standardnotes/app/commit/767d3547804bc98693dcc769baeed943c24426f3))
|
||||
|
||||
## [1.47.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.47.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.47.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [1.46.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.46.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.46.0",
|
||||
"version": "1.50.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint:fix": "eslint src --ext .ts --fix",
|
||||
"test": "jest spec --coverage"
|
||||
"test": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/api": "workspace:^",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { StatusServiceInterface } from './../Status/StatusServiceInterface'
|
||||
import { FilesBackupService } from './BackupService'
|
||||
import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { InternalEventBusInterface } from '..'
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
import { ApiServiceInterface } from '../Api/ApiServiceInterface'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { FileBackupsDevice } from '@standardnotes/files'
|
||||
|
||||
describe('backup service', () => {
|
||||
let apiService: ApiServiceInterface
|
||||
let itemManager: ItemManagerInterface
|
||||
let syncService: SyncServiceInterface
|
||||
let alertService: AlertService
|
||||
let crypto: PureCryptoInterface
|
||||
let status: StatusServiceInterface
|
||||
let encryptor: EncryptionProviderInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let backupService: FilesBackupService
|
||||
let device: FileBackupsDevice
|
||||
|
||||
beforeEach(() => {
|
||||
apiService = {} as jest.Mocked<ApiServiceInterface>
|
||||
apiService.addEventObserver = jest.fn()
|
||||
apiService.createFileValetToken = jest.fn()
|
||||
apiService.downloadFile = jest.fn()
|
||||
apiService.deleteFile = jest.fn().mockReturnValue({})
|
||||
|
||||
itemManager = {} as jest.Mocked<ItemManagerInterface>
|
||||
itemManager.createItem = jest.fn()
|
||||
itemManager.createTemplateItem = jest.fn().mockReturnValue({})
|
||||
itemManager.setItemToBeDeleted = jest.fn()
|
||||
itemManager.addObserver = jest.fn()
|
||||
itemManager.changeItem = jest.fn()
|
||||
|
||||
status = {} as jest.Mocked<StatusServiceInterface>
|
||||
|
||||
device = {} as jest.Mocked<FileBackupsDevice>
|
||||
device.getFileBackupReadToken = jest.fn()
|
||||
device.readNextChunk = jest.fn()
|
||||
|
||||
syncService = {} as jest.Mocked<SyncServiceInterface>
|
||||
syncService.sync = jest.fn()
|
||||
|
||||
encryptor = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
|
||||
alertService = {} as jest.Mocked<AlertService>
|
||||
alertService.confirm = jest.fn().mockReturnValue(true)
|
||||
alertService.alert = jest.fn()
|
||||
|
||||
crypto = {} as jest.Mocked<PureCryptoInterface>
|
||||
crypto.base64Decode = jest.fn()
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
backupService = new FilesBackupService(itemManager, apiService, encryptor, device, status, crypto, internalEventBus)
|
||||
|
||||
crypto.xchacha20StreamInitDecryptor = jest.fn().mockReturnValue({
|
||||
state: {},
|
||||
} as StreamEncryptor)
|
||||
|
||||
crypto.xchacha20StreamDecryptorPush = jest.fn().mockReturnValue({ message: new Uint8Array([0xaa]), tag: 0 })
|
||||
|
||||
crypto.xchacha20StreamInitEncryptor = jest.fn().mockReturnValue({
|
||||
header: 'some-header',
|
||||
state: {},
|
||||
} as StreamEncryptor)
|
||||
|
||||
crypto.xchacha20StreamEncryptorPush = jest.fn().mockReturnValue(new Uint8Array())
|
||||
})
|
||||
|
||||
describe('readEncryptedFileFromBackup', () => {
|
||||
it('return failed if no backup', async () => {
|
||||
backupService.getFileBackupInfo = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
const result = await backupService.readEncryptedFileFromBackup('123', async () => {})
|
||||
|
||||
expect(result).toEqual('failed')
|
||||
})
|
||||
|
||||
it('return success if backup', async () => {
|
||||
backupService.getFileBackupInfo = jest.fn().mockReturnValue({})
|
||||
|
||||
device.readNextChunk = jest.fn().mockReturnValue({ chunk: new Uint8Array([]), isLast: true, progress: undefined })
|
||||
|
||||
const result = await backupService.readEncryptedFileFromBackup('123', async () => {})
|
||||
|
||||
expect(result).toEqual('success')
|
||||
})
|
||||
|
||||
it('should loop through all chunks until last', async () => {
|
||||
backupService.getFileBackupInfo = jest.fn().mockReturnValue({})
|
||||
const expectedChunkCount = 3
|
||||
let receivedChunkCount = 0
|
||||
|
||||
const mockFn = (device.readNextChunk = jest.fn().mockImplementation(() => {
|
||||
receivedChunkCount++
|
||||
|
||||
return { chunk: new Uint8Array([]), isLast: receivedChunkCount === expectedChunkCount, progress: undefined }
|
||||
}))
|
||||
|
||||
await backupService.readEncryptedFileFromBackup('123', async () => {})
|
||||
|
||||
expect(mockFn.mock.calls.length).toEqual(expectedChunkCount)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -8,13 +8,17 @@ import {
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupRecord,
|
||||
OnChunkCallback,
|
||||
BackupServiceInterface,
|
||||
} from '@standardnotes/files'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { StatusServiceInterface } from '../Status/StatusServiceInterface'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { log, LoggingDomain } from '../Logging'
|
||||
|
||||
export class FilesBackupService extends AbstractService {
|
||||
export class FilesBackupService extends AbstractService implements BackupServiceInterface {
|
||||
private itemsObserverDisposer: () => void
|
||||
private pendingFiles = new Set<Uuid>()
|
||||
private mappingCache?: FileBackupsMapping['files']
|
||||
@@ -25,6 +29,7 @@ export class FilesBackupService extends AbstractService {
|
||||
private encryptor: EncryptionProviderInterface,
|
||||
private device: FileBackupsDevice,
|
||||
private status: StatusServiceInterface,
|
||||
private crypto: PureCryptoInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -43,7 +48,14 @@ export class FilesBackupService extends AbstractService {
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
super.deinit()
|
||||
this.itemsObserverDisposer()
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.api as unknown) = undefined
|
||||
;(this.encryptor as unknown) = undefined
|
||||
;(this.device as unknown) = undefined
|
||||
;(this.status as unknown) = undefined
|
||||
;(this.crypto as unknown) = undefined
|
||||
}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
@@ -98,7 +110,7 @@ export class FilesBackupService extends AbstractService {
|
||||
return this.mappingCache ?? (await this.getBackupsMappingFromDisk())
|
||||
}
|
||||
|
||||
public async getFileBackupInfo(file: FileItem): Promise<FileBackupRecord | undefined> {
|
||||
public async getFileBackupInfo(file: { uuid: string }): Promise<FileBackupRecord | undefined> {
|
||||
const mapping = await this.getBackupsMappingFromCache()
|
||||
const record = mapping[file.uuid]
|
||||
return record
|
||||
@@ -138,7 +150,34 @@ export class FilesBackupService extends AbstractService {
|
||||
this.invalidateMappingCache()
|
||||
}
|
||||
|
||||
async readEncryptedFileFromBackup(uuid: string, onChunk: OnChunkCallback): Promise<'success' | 'failed' | 'aborted'> {
|
||||
const fileBackup = await this.getFileBackupInfo({ uuid })
|
||||
|
||||
if (!fileBackup) {
|
||||
return 'failed'
|
||||
}
|
||||
|
||||
const token = await this.device.getFileBackupReadToken(fileBackup)
|
||||
|
||||
let readMore = true
|
||||
let index = 0
|
||||
|
||||
while (readMore) {
|
||||
const { chunk, isLast, progress } = await this.device.readNextChunk(token)
|
||||
|
||||
await onChunk({ data: chunk, index, isLast, progress })
|
||||
|
||||
readMore = !isLast
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
return 'success'
|
||||
}
|
||||
|
||||
private async performBackupOperation(file: FileItem): Promise<'success' | 'failed' | 'aborted'> {
|
||||
log(LoggingDomain.FilesBackups, 'Backing up file locally', file.uuid)
|
||||
|
||||
const messageId = this.status.addMessage(`Backing up file ${file.name}...`)
|
||||
|
||||
const encryptedFile = await this.encryptor.encryptSplitSingle({
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common'
|
||||
import { FileItem } from '@standardnotes/models'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { ChallengeServiceInterface } from '../Challenge'
|
||||
import { InternalEventBusInterface } from '..'
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
import { ApiServiceInterface } from '../Api/ApiServiceInterface'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
|
||||
import { FileService } from './FileService'
|
||||
import { BackupServiceInterface } from '@standardnotes/files'
|
||||
|
||||
describe('fileService', () => {
|
||||
let apiService: ApiServiceInterface
|
||||
@@ -21,13 +20,33 @@ describe('fileService', () => {
|
||||
let fileService: FileService
|
||||
let encryptor: EncryptionProviderInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let backupService: BackupServiceInterface
|
||||
|
||||
beforeEach(() => {
|
||||
apiService = {} as jest.Mocked<ApiServiceInterface>
|
||||
apiService.addEventObserver = jest.fn()
|
||||
apiService.createFileValetToken = jest.fn()
|
||||
apiService.downloadFile = jest.fn()
|
||||
apiService.deleteFile = jest.fn().mockReturnValue({})
|
||||
const numChunks = 1
|
||||
apiService.downloadFile = jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(
|
||||
_file: string,
|
||||
_chunkIndex: number,
|
||||
_apiToken: string,
|
||||
_rangeStart: number,
|
||||
onBytesReceived: (bytes: Uint8Array) => void,
|
||||
) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
for (let i = 0; i < numChunks; i++) {
|
||||
onBytesReceived(Uint8Array.from([0xaa]))
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
itemManager = {} as jest.Mocked<ItemManagerInterface>
|
||||
itemManager.createItem = jest.fn()
|
||||
@@ -52,6 +71,10 @@ describe('fileService', () => {
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
backupService = {} as jest.Mocked<BackupServiceInterface>
|
||||
backupService.readEncryptedFileFromBackup = jest.fn()
|
||||
backupService.getFileBackupInfo = jest.fn()
|
||||
|
||||
fileService = new FileService(
|
||||
apiService,
|
||||
itemManager,
|
||||
@@ -61,6 +84,7 @@ describe('fileService', () => {
|
||||
alertService,
|
||||
crypto,
|
||||
internalEventBus,
|
||||
backupService,
|
||||
)
|
||||
|
||||
crypto.xchacha20StreamInitDecryptor = jest.fn().mockReturnValue({
|
||||
@@ -110,6 +134,8 @@ describe('fileService', () => {
|
||||
decryptedSize: 100_000,
|
||||
} as jest.Mocked<FileItem>
|
||||
|
||||
apiService.downloadFile = jest.fn()
|
||||
|
||||
await fileService.downloadFile(file, async () => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
@@ -118,4 +144,42 @@ describe('fileService', () => {
|
||||
|
||||
expect(fileService['encryptedCache'].get(file.uuid)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should download file from network if no backup', async () => {
|
||||
const file = {
|
||||
uuid: '1',
|
||||
decryptedSize: 100_000,
|
||||
encryptedSize: 101_000,
|
||||
encryptedChunkSizes: [101_000],
|
||||
} as jest.Mocked<FileItem>
|
||||
|
||||
backupService.getFileBackupInfo = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
const downloadMock = apiService.downloadFile as jest.Mock
|
||||
|
||||
await fileService.downloadFile(file, async () => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
expect(downloadMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should download file from local backup if it exists', async () => {
|
||||
const file = {
|
||||
uuid: '1',
|
||||
decryptedSize: 100_000,
|
||||
encryptedSize: 101_000,
|
||||
encryptedChunkSizes: [101_000],
|
||||
} as jest.Mocked<FileItem>
|
||||
|
||||
backupService.getFileBackupInfo = jest.fn().mockReturnValue({})
|
||||
|
||||
const downloadMock = (apiService.downloadFile = jest.fn())
|
||||
|
||||
await fileService.downloadFile(file, async () => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
expect(downloadMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
FileDecryptor,
|
||||
FileDownloadProgress,
|
||||
FilesClientInterface,
|
||||
readAndDecryptBackupFile,
|
||||
readAndDecryptBackupFileUsingFileSystemAPI,
|
||||
FilesApiInterface,
|
||||
FileBackupsConstantsV1,
|
||||
FileBackupMetadataFile,
|
||||
@@ -30,8 +30,9 @@ import {
|
||||
DecryptedBytes,
|
||||
OrderedByteChunker,
|
||||
FileMemoryCache,
|
||||
readAndDecryptBackupFileUsingBackupService,
|
||||
BackupServiceInterface,
|
||||
} from '@standardnotes/files'
|
||||
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
import { ChallengeServiceInterface } from '../Challenge'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
@@ -39,6 +40,7 @@ import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { DecryptItemsKeyWithUserFallback } from '../Encryption/Functions'
|
||||
import { log, LoggingDomain } from '../Logging'
|
||||
|
||||
const OneHundredMb = 100 * 1_000_000
|
||||
|
||||
@@ -54,6 +56,7 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
private alertService: AlertService,
|
||||
private crypto: PureCryptoInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
private backupsService?: BackupServiceInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
}
|
||||
@@ -158,8 +161,8 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
|
||||
let decryptedAggregate = new Uint8Array()
|
||||
|
||||
const orderedChunker = new OrderedByteChunker(file.encryptedChunkSizes, async (encryptedBytes) => {
|
||||
const decryptedBytes = decryptOperation.decryptBytes(encryptedBytes)
|
||||
const orderedChunker = new OrderedByteChunker(file.encryptedChunkSizes, 'memcache', async (chunk) => {
|
||||
const decryptedBytes = decryptOperation.decryptBytes(chunk.data)
|
||||
|
||||
if (decryptedBytes) {
|
||||
decryptedAggregate = new Uint8Array([...decryptedAggregate, ...decryptedBytes.decryptedBytes])
|
||||
@@ -173,36 +176,60 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
|
||||
public async downloadFile(
|
||||
file: FileItem,
|
||||
onDecryptedBytes: (decryptedBytes: Uint8Array, progress?: FileDownloadProgress) => Promise<void>,
|
||||
onDecryptedBytes: (decryptedBytes: Uint8Array, progress: FileDownloadProgress) => Promise<void>,
|
||||
): Promise<ClientDisplayableError | undefined> {
|
||||
const cachedBytes = this.encryptedCache.get(file.uuid)
|
||||
|
||||
if (cachedBytes) {
|
||||
const decryptedBytes = await this.decryptCachedEntry(file, cachedBytes)
|
||||
|
||||
await onDecryptedBytes(decryptedBytes.decryptedBytes, undefined)
|
||||
await onDecryptedBytes(decryptedBytes.decryptedBytes, {
|
||||
encryptedFileSize: cachedBytes.encryptedBytes.length,
|
||||
encryptedBytesDownloaded: cachedBytes.encryptedBytes.length,
|
||||
encryptedBytesRemaining: 0,
|
||||
percentComplete: 100,
|
||||
source: 'memcache',
|
||||
})
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
const addToCache = file.encryptedSize < this.encryptedCache.maxSize
|
||||
const fileBackup = await this.backupsService?.getFileBackupInfo(file)
|
||||
|
||||
let cacheEntryAggregate = new Uint8Array()
|
||||
if (this.backupsService && fileBackup) {
|
||||
log(LoggingDomain.FilesService, 'Downloading file from backup', fileBackup)
|
||||
|
||||
const operation = new DownloadAndDecryptFileOperation(file, this.crypto, this.api)
|
||||
await readAndDecryptBackupFileUsingBackupService(file, this.backupsService, this.crypto, async (chunk) => {
|
||||
log(LoggingDomain.FilesService, 'Got local file chunk', chunk.progress)
|
||||
|
||||
const result = await operation.run(async ({ decrypted, encrypted, progress }): Promise<void> => {
|
||||
if (addToCache) {
|
||||
cacheEntryAggregate = new Uint8Array([...cacheEntryAggregate, ...encrypted.encryptedBytes])
|
||||
return onDecryptedBytes(chunk.data, chunk.progress)
|
||||
})
|
||||
|
||||
log(LoggingDomain.FilesService, 'Finished downloading file from backup')
|
||||
|
||||
return undefined
|
||||
} else {
|
||||
log(LoggingDomain.FilesService, 'Downloading file from network')
|
||||
|
||||
const addToCache = file.encryptedSize < this.encryptedCache.maxSize
|
||||
|
||||
let cacheEntryAggregate = new Uint8Array()
|
||||
|
||||
const operation = new DownloadAndDecryptFileOperation(file, this.crypto, this.api)
|
||||
|
||||
const result = await operation.run(async ({ decrypted, encrypted, progress }): Promise<void> => {
|
||||
if (addToCache) {
|
||||
cacheEntryAggregate = new Uint8Array([...cacheEntryAggregate, ...encrypted.encryptedBytes])
|
||||
}
|
||||
return onDecryptedBytes(decrypted.decryptedBytes, progress)
|
||||
})
|
||||
|
||||
if (addToCache && cacheEntryAggregate.byteLength > 0) {
|
||||
this.encryptedCache.add(file.uuid, { encryptedBytes: cacheEntryAggregate })
|
||||
}
|
||||
return onDecryptedBytes(decrypted.decryptedBytes, progress)
|
||||
})
|
||||
|
||||
if (addToCache) {
|
||||
this.encryptedCache.add(file.uuid, { encryptedBytes: cacheEntryAggregate })
|
||||
return result.error
|
||||
}
|
||||
|
||||
return result.error
|
||||
}
|
||||
|
||||
public async deleteFile(file: FileItem): Promise<ClientDisplayableError | undefined> {
|
||||
@@ -294,9 +321,15 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
return destinationFileHandle
|
||||
}
|
||||
|
||||
const result = await readAndDecryptBackupFile(fileHandle, file, fileSystem, this.crypto, async (decryptedBytes) => {
|
||||
await fileSystem.saveBytes(destinationFileHandle, decryptedBytes)
|
||||
})
|
||||
const result = await readAndDecryptBackupFileUsingFileSystemAPI(
|
||||
fileHandle,
|
||||
file,
|
||||
fileSystem,
|
||||
this.crypto,
|
||||
async (decryptedBytes) => {
|
||||
await fileSystem.saveBytes(destinationFileHandle, decryptedBytes)
|
||||
},
|
||||
)
|
||||
|
||||
await fileSystem.closeFileWriteStream(destinationFileHandle)
|
||||
|
||||
@@ -310,9 +343,15 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
): Promise<Uint8Array> {
|
||||
let bytes = new Uint8Array()
|
||||
|
||||
await readAndDecryptBackupFile(fileHandle, file, fileSystem, this.crypto, async (decryptedBytes) => {
|
||||
bytes = new Uint8Array([...bytes, ...decryptedBytes])
|
||||
})
|
||||
await readAndDecryptBackupFileUsingFileSystemAPI(
|
||||
fileHandle,
|
||||
file,
|
||||
fileSystem,
|
||||
this.crypto,
|
||||
async (decryptedBytes) => {
|
||||
bytes = new Uint8Array([...bytes, ...decryptedBytes])
|
||||
},
|
||||
)
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface ItemsClientInterface {
|
||||
addNoteCountChangeObserver(observer: TagItemCountChangeObserver): () => void
|
||||
|
||||
allCountableNotesCount(): number
|
||||
allCountableFilesCount(): number
|
||||
|
||||
countableNotesForTag(tag: SNTag | SmartView): number
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { logWithColor } from '@standardnotes/utils'
|
||||
|
||||
declare const process: {
|
||||
env: {
|
||||
NODE_ENV: string | null | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'
|
||||
|
||||
export enum LoggingDomain {
|
||||
FilesService,
|
||||
FilesBackups,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.FilesService]: false,
|
||||
[LoggingDomain.FilesBackups]: false,
|
||||
}
|
||||
|
||||
const LoggingColor: Record<LoggingDomain, string> = {
|
||||
[LoggingDomain.FilesService]: 'blue',
|
||||
[LoggingDomain.FilesBackups]: 'yellow',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDev || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
logWithColor(LoggingDomain[domain], LoggingColor[domain], ...args)
|
||||
}
|
||||
@@ -39,6 +39,8 @@ export enum StorageKey {
|
||||
DeinitMode = 'deinit_mode',
|
||||
CodeVerifier = 'code_verifier',
|
||||
LaunchPriorityUuids = 'launch_priority_uuids',
|
||||
LastReadChangelogVersion = 'last_read_changelog_version',
|
||||
MomentsEnabled = 'moments_enabled',
|
||||
}
|
||||
|
||||
export enum NonwrappedStorageKey {
|
||||
|
||||
@@ -3,6 +3,88 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.155.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.155.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
* download and preview files from local backups automatically, if a local backup is available ([#2076](https://github.com/standardnotes/app/issues/2076)) ([28e43d3](https://github.com/standardnotes/app/commit/28e43d37c00c5cd97129c4b83266f7a234bd9857))
|
||||
|
||||
## [2.154.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.154.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* display number of files for 'Files' view ([#2065](https://github.com/standardnotes/app/issues/2065)) ([767d354](https://github.com/standardnotes/app/commit/767d3547804bc98693dcc769baeed943c24426f3))
|
||||
|
||||
# [2.153.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* convert Super notes to Markdown behind the scenes while publishing to Listed ([#2064](https://github.com/standardnotes/app/issues/2064)) ([cc2762a](https://github.com/standardnotes/app/commit/cc2762a29d83e2e4cd646345f9d3935571b27c62))
|
||||
|
||||
## [2.152.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exclude files from being counted in 'Notes' view ([#2062](https://github.com/standardnotes/app/issues/2062)) ([2b0c9b1](https://github.com/standardnotes/app/commit/2b0c9b188cd75477c0c1611443daadb8ffa29306))
|
||||
|
||||
## [2.152.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.152.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** initial value of subscription ids on files test - run e2e ([81b5123](https://github.com/standardnotes/app/commit/81b51232940b5fade61ad6e72237f739607bed2b))
|
||||
* **snjs:** subscription id increments - run e2e ([7a89a3c](https://github.com/standardnotes/app/commit/7a89a3cd9cb2c364d3b22da96c894040bcf7167b))
|
||||
|
||||
## [2.152.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** add additional sign in missing - run e2e ([6bbf0c0](https://github.com/standardnotes/app/commit/6bbf0c02bda0350772638ee19424420b6430dec3))
|
||||
* **snjs:** decrease factory sleep in order to have a continuing session - run e2e ([0fac2dd](https://github.com/standardnotes/app/commit/0fac2dd93974f7072e5e3a73ef705836e163dcac))
|
||||
* **snjs:** remove extra sign in - run e2e ([212e784](https://github.com/standardnotes/app/commit/212e7843ec2ec565c46e06a3acd52a7bbfc16f0b))
|
||||
* **snjs:** run e2e ([75303b5](https://github.com/standardnotes/app/commit/75303b5eb8c1e403541bca57b20ae293cb67f979))
|
||||
|
||||
## [2.152.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* subscription expiring event ([07e2691](https://github.com/standardnotes/app/commit/07e26915b07656e813d439a24f8d4682ba6773af))
|
||||
|
||||
# [2.152.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** factory timeout to propagate subscription purchase ([c40b17b](https://github.com/standardnotes/app/commit/c40b17bd4cdb943b299e3cb17f0cbb5ce8e736e2))
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
## [2.151.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** adjust file bytes used expected ([e36a1ff](https://github.com/standardnotes/app/commit/e36a1ffea0aba9e860c511fa64e0b0ad87890b30))
|
||||
* **snjs:** file subscriptions tests subscription assignment ([25cf4cf](https://github.com/standardnotes/app/commit/25cf4cfd2210b4032f362b4ec86ff6c6aa485891))
|
||||
* **snjs:** remove unnecessary file uploaded event in tests ([b05c793](https://github.com/standardnotes/app/commit/b05c793fe9c0016ce744eace14871291511399a6))
|
||||
* **snjs:** subscription events in tests ([c454176](https://github.com/standardnotes/app/commit/c45417623cc65f4369183069b9df0ba97e9f9965))
|
||||
* **snjs:** wait timeout after subscription purchase ([99163d9](https://github.com/standardnotes/app/commit/99163d90d2084d118433afe311362a1f943087b7))
|
||||
|
||||
## [2.151.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.151.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -64,7 +64,7 @@ import {
|
||||
SessionStrings,
|
||||
AccountEvent,
|
||||
} from '@standardnotes/services'
|
||||
import { FilesClientInterface } from '@standardnotes/files'
|
||||
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
|
||||
import { ComputePrivateUsername } from '@standardnotes/encryption'
|
||||
import { useBoolean } from '@standardnotes/utils'
|
||||
import {
|
||||
@@ -275,7 +275,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.statusService
|
||||
}
|
||||
|
||||
public get fileBackups(): FilesBackupService | undefined {
|
||||
public get fileBackups(): BackupServiceInterface | undefined {
|
||||
return this.filesBackupService
|
||||
}
|
||||
|
||||
@@ -291,6 +291,14 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.alertService
|
||||
}
|
||||
|
||||
public get storage(): ExternalServices.StorageServiceInterface {
|
||||
return this.diskStorageService
|
||||
}
|
||||
|
||||
public get actions(): InternalServices.SNActionsService {
|
||||
return this.actionsManager
|
||||
}
|
||||
|
||||
public computePrivateUsername(username: string): Promise<string | undefined> {
|
||||
return ComputePrivateUsername(this.options.crypto, username)
|
||||
}
|
||||
@@ -1105,16 +1113,17 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.createComponentManager()
|
||||
this.createMigrationService()
|
||||
this.createMfaService()
|
||||
|
||||
this.createStatusService()
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
this.createFilesBackupService(this.deviceInterface)
|
||||
}
|
||||
this.createFileService()
|
||||
|
||||
this.createIntegrityService()
|
||||
this.createMutatorService()
|
||||
this.createListedService()
|
||||
this.createActionsManager()
|
||||
this.createStatusService()
|
||||
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
this.createFilesBackupService(this.deviceInterface)
|
||||
}
|
||||
}
|
||||
|
||||
private clearServices() {
|
||||
@@ -1202,6 +1211,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.alertService,
|
||||
this.options.crypto,
|
||||
this.internalEventBus,
|
||||
this.fileBackups,
|
||||
)
|
||||
|
||||
this.services.push(this.fileService)
|
||||
@@ -1375,7 +1385,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
}
|
||||
|
||||
private createItemManager() {
|
||||
this.itemManager = new InternalServices.ItemManager(this.payloadManager, this.options, this.internalEventBus)
|
||||
this.itemManager = new InternalServices.ItemManager(this.payloadManager, this.internalEventBus)
|
||||
this.services.push(this.itemManager)
|
||||
}
|
||||
|
||||
@@ -1662,6 +1672,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.protocolService,
|
||||
device,
|
||||
this.statusService,
|
||||
this.options.crypto,
|
||||
this.internalEventBus,
|
||||
)
|
||||
this.services.push(this.filesBackupService)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions'
|
||||
import { ApplicationSyncOptions } from './OptionalOptions'
|
||||
|
||||
export interface ApplicationOptionsWhichHaveDefaults {
|
||||
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
|
||||
supportsFileNavigation: ApplicationDisplayOptions['supportsFileNavigation']
|
||||
}
|
||||
|
||||
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
|
||||
loadBatchSize: 700,
|
||||
supportsFileNavigation: false,
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ export interface ApplicationSyncOptions {
|
||||
loadBatchSize: number
|
||||
}
|
||||
|
||||
export interface ApplicationDisplayOptions {
|
||||
supportsFileNavigation: boolean
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ApplicationDisplayOptions {}
|
||||
|
||||
export interface ApplicationOptionalConfiguratioOptions {
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { SNRootKey } from '@standardnotes/encryption'
|
||||
import { ChallengeService } from '../Challenge'
|
||||
import { ListedService } from '../Listed/ListedService'
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
isErrorDecryptingPayload,
|
||||
CreateEncryptedBackupFileContextPayload,
|
||||
EncryptedTransferPayload,
|
||||
TransferPayload,
|
||||
ItemContent,
|
||||
} from '@standardnotes/models'
|
||||
import { SNSyncService } from '../Sync/SyncService'
|
||||
import { PayloadManager } from '../Payloads/PayloadManager'
|
||||
@@ -34,6 +37,8 @@ import {
|
||||
Challenge,
|
||||
} from '@standardnotes/services'
|
||||
|
||||
type PayloadRequestHandler = (uuid: string) => TransferPayload | undefined
|
||||
|
||||
/**
|
||||
* The Actions Service allows clients to interact with action-based extensions.
|
||||
* Action-based extensions are mostly RESTful actions that can push a local value or
|
||||
@@ -50,6 +55,7 @@ import {
|
||||
*/
|
||||
export class SNActionsService extends AbstractService {
|
||||
private previousPasswords: string[] = []
|
||||
private payloadRequestHandlers: PayloadRequestHandler[] = []
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManager,
|
||||
@@ -77,10 +83,19 @@ export class SNActionsService extends AbstractService {
|
||||
;(this.challengeService as unknown) = undefined
|
||||
;(this.protocolService as unknown) = undefined
|
||||
;(this.syncService as unknown) = undefined
|
||||
this.payloadRequestHandlers.length = 0
|
||||
this.previousPasswords.length = 0
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
public addPayloadRequestHandler(handler: PayloadRequestHandler) {
|
||||
this.payloadRequestHandlers.push(handler)
|
||||
|
||||
return () => {
|
||||
removeFromArray(this.payloadRequestHandlers, handler)
|
||||
}
|
||||
}
|
||||
|
||||
public getExtensions(): SNActionsExtension[] {
|
||||
const extensionItems = this.itemManager.getItems<SNActionsExtension>(ContentType.ActionsExtension)
|
||||
const excludingListed = extensionItems.filter((extension) => !extension.isListedExtension)
|
||||
@@ -312,7 +327,16 @@ export class SNActionsService extends AbstractService {
|
||||
return {} as ActionResponse
|
||||
}
|
||||
|
||||
private async outgoingPayloadForItem(item: DecryptedItemInterface, decrypted = false) {
|
||||
private async outgoingPayloadForItem(
|
||||
item: DecryptedItemInterface,
|
||||
decrypted = false,
|
||||
): Promise<TransferPayload<ItemContent>> {
|
||||
const payloadFromHandler = this.getPayloadFromRequestHandlers(item.uuid)
|
||||
|
||||
if (payloadFromHandler) {
|
||||
return payloadFromHandler
|
||||
}
|
||||
|
||||
if (decrypted) {
|
||||
return item.payload.ejected()
|
||||
}
|
||||
@@ -323,4 +347,15 @@ export class SNActionsService extends AbstractService {
|
||||
|
||||
return CreateEncryptedBackupFileContextPayload(encrypted)
|
||||
}
|
||||
|
||||
private getPayloadFromRequestHandlers(uuid: string): TransferPayload | undefined {
|
||||
for (const handler of this.payloadRequestHandlers) {
|
||||
const payload = handler(uuid)
|
||||
if (payload) {
|
||||
return payload
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,19 +38,21 @@ export class SNWebSocketsService extends AbstractService<WebSocketsServiceEvent,
|
||||
}
|
||||
|
||||
async startWebSocketConnection(): Promise<void> {
|
||||
if (!this.webSocketUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
const webSocketConectionToken = await this.createWebSocketConnectionToken()
|
||||
if (webSocketConectionToken === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.webSocketUrl) {
|
||||
try {
|
||||
this.webSocket = new WebSocket(`${this.webSocketUrl}?authToken=${webSocketConectionToken}`)
|
||||
this.webSocket.onmessage = this.onWebSocketMessage.bind(this)
|
||||
this.webSocket.onclose = this.onWebSocketClose.bind(this)
|
||||
} catch (e) {
|
||||
console.error('Error starting WebSocket connection', e)
|
||||
}
|
||||
try {
|
||||
this.webSocket = new WebSocket(`${this.webSocketUrl}?authToken=${webSocketConectionToken}`)
|
||||
this.webSocket.onmessage = this.onWebSocketMessage.bind(this)
|
||||
this.webSocket.onclose = this.onWebSocketClose.bind(this)
|
||||
} catch (e) {
|
||||
console.error('Error starting WebSocket connection', e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('itemManager', () => {
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
|
||||
const createService = () => {
|
||||
return new ItemManager(payloadManager, { supportsFileNavigation: false }, internalEventBus)
|
||||
return new ItemManager(payloadManager, internalEventBus)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as Models from '@standardnotes/models'
|
||||
import * as Services from '@standardnotes/services'
|
||||
import { PayloadManagerChangeData } from '../Payloads'
|
||||
import { DiagnosticInfo, ItemsClientInterface, ItemRelationshipDirection } from '@standardnotes/services'
|
||||
import { ApplicationDisplayOptions } from '@Lib/Application/Options/OptionalOptions'
|
||||
import { CollectionSort, DecryptedItemInterface, ItemContent, SmartViewDefaultIconName } from '@standardnotes/models'
|
||||
|
||||
type ItemsChangeObserver<I extends Models.DecryptedItemInterface = Models.DecryptedItemInterface> = {
|
||||
@@ -44,7 +43,6 @@ export class ItemManager
|
||||
|
||||
constructor(
|
||||
private payloadManager: PayloadManager,
|
||||
private readonly options: ApplicationDisplayOptions = { supportsFileNavigation: false },
|
||||
protected override internalEventBus: Services.InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -55,7 +53,7 @@ export class ItemManager
|
||||
}
|
||||
|
||||
private rebuildSystemSmartViews(criteria: Models.FilterDisplayOptions): Models.SmartView[] {
|
||||
this.systemSmartViews = Models.BuildSmartViews(criteria, this.options)
|
||||
this.systemSmartViews = Models.BuildSmartViews(criteria)
|
||||
return this.systemSmartViews
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ export class ItemManager
|
||||
{
|
||||
sortBy: 'created_at',
|
||||
sortDirection: 'dsc',
|
||||
hiddenContentTypes: !this.options.supportsFileNavigation ? [ContentType.File] : [],
|
||||
hiddenContentTypes: [],
|
||||
},
|
||||
)
|
||||
this.tagDisplayController = new Models.ItemDisplayController(this.collection, [ContentType.Tag], {
|
||||
@@ -184,12 +182,7 @@ export class ItemManager
|
||||
public getDisplayableNotes(): Models.SNNote[] {
|
||||
assert(this.navigationDisplayController.contentTypes.length === 2)
|
||||
|
||||
const fileContentTypeHidden = !this.options.supportsFileNavigation
|
||||
if (fileContentTypeHidden) {
|
||||
return this.navigationDisplayController.items() as Models.SNNote[]
|
||||
} else {
|
||||
return this.navigationDisplayController.items().filter(Models.isNote)
|
||||
}
|
||||
return this.navigationDisplayController.items().filter(Models.isNote)
|
||||
}
|
||||
|
||||
public getDisplayableFiles(): Models.FileItem[] {
|
||||
@@ -197,7 +190,6 @@ export class ItemManager
|
||||
}
|
||||
|
||||
public getDisplayableNotesAndFiles(): (Models.SNNote | Models.FileItem)[] {
|
||||
assert(this.options.supportsFileNavigation)
|
||||
return this.navigationDisplayController.items()
|
||||
}
|
||||
|
||||
@@ -215,7 +207,6 @@ export class ItemManager
|
||||
|
||||
public override deinit(): void {
|
||||
this.unsubChangeObserver()
|
||||
;(this.options as unknown) = undefined
|
||||
;(this.unsubChangeObserver as unknown) = undefined
|
||||
;(this.payloadManager as unknown) = undefined
|
||||
;(this.collection as unknown) = undefined
|
||||
@@ -289,13 +280,17 @@ export class ItemManager
|
||||
}
|
||||
|
||||
public allCountableNotesCount(): number {
|
||||
return this.tagItemsIndex.allCountableItemsCount()
|
||||
return this.tagItemsIndex.allCountableNotesCount()
|
||||
}
|
||||
|
||||
public allCountableFilesCount(): number {
|
||||
return this.tagItemsIndex.allCountableFilesCount()
|
||||
}
|
||||
|
||||
public countableNotesForTag(tag: Models.SNTag | Models.SmartView): number {
|
||||
if (tag instanceof Models.SmartView) {
|
||||
if (tag.uuid === Models.SystemViewId.AllNotes) {
|
||||
return this.tagItemsIndex.allCountableItemsCount()
|
||||
return this.tagItemsIndex.allCountableNotesCount()
|
||||
}
|
||||
|
||||
throw Error('countableItemsForTag is not meant to be used for smart views.')
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('mutator service', () => {
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(internalEventBus)
|
||||
itemManager = new ItemManager(payloadManager, { supportsFileNavigation: false }, internalEventBus)
|
||||
itemManager = new ItemManager(payloadManager, internalEventBus)
|
||||
|
||||
mutatorService = new MutatorService(
|
||||
itemManager,
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('files', function () {
|
||||
let context
|
||||
let fileService
|
||||
let itemManager
|
||||
let subscriptionId = 1001
|
||||
|
||||
beforeEach(function () {
|
||||
localStorage.clear()
|
||||
@@ -39,13 +40,20 @@ describe('files', function () {
|
||||
if (subscription) {
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionName: 'PLUS_PLAN',
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(0.25)
|
||||
await Factory.sleep(2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +64,7 @@ describe('files', function () {
|
||||
|
||||
it('should create valet token from server', async function () {
|
||||
await setup({ fakeCrypto: true, subscription: true })
|
||||
|
||||
const remoteIdentifier = Utils.generateUuid()
|
||||
const token = await application.apiService.createFileValetToken(remoteIdentifier, 'write')
|
||||
|
||||
@@ -76,14 +85,21 @@ describe('files', function () {
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PLUS_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() - 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
|
||||
await Factory.sleep(0.25)
|
||||
await Factory.sleep(2)
|
||||
|
||||
const remoteIdentifier = Utils.generateUuid()
|
||||
const tokenOrError = await application.apiService.createFileValetToken(remoteIdentifier, 'write')
|
||||
@@ -149,10 +165,4 @@ describe('files', function () {
|
||||
|
||||
expect(downloadError).to.be.ok
|
||||
})
|
||||
|
||||
it.skip('should cancel file download', async function () {
|
||||
await setup({ fakeCrypto: false, subscription: true })
|
||||
|
||||
// ...
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('settings service', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
this.timeout(Factory.ThirtySecondTimeout)
|
||||
|
||||
const validSetting = SettingName.GoogleDriveBackupFrequency
|
||||
const fakePayload = 'Im so meta even this acronym'
|
||||
@@ -13,21 +13,21 @@ describe('settings service', function () {
|
||||
|
||||
let application
|
||||
let context
|
||||
let user
|
||||
let subscriptionId = 2001
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
context = await Factory.createAppContextWithFakeCrypto()
|
||||
|
||||
await context.launch()
|
||||
|
||||
application = context.application
|
||||
|
||||
const registerResponse = await Factory.registerUserToApplication({
|
||||
await Factory.registerUserToApplication({
|
||||
application: context.application,
|
||||
email: context.email,
|
||||
password: context.password,
|
||||
})
|
||||
user = registerResponse.user
|
||||
})
|
||||
|
||||
const reInitializeApplicationWithRealCrypto = async () => {
|
||||
@@ -39,16 +39,16 @@ describe('settings service', function () {
|
||||
|
||||
application = context.application
|
||||
|
||||
const registerResponse = await Factory.registerUserToApplication({
|
||||
await Factory.registerUserToApplication({
|
||||
application: context.application,
|
||||
email: context.email,
|
||||
password: context.password,
|
||||
})
|
||||
user = registerResponse.user
|
||||
}
|
||||
|
||||
afterEach(async function () {
|
||||
await Factory.safeDeinit(application)
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('creates and reads a setting', async function () {
|
||||
@@ -119,14 +119,21 @@ describe('settings service', function () {
|
||||
it('reads a subscription setting', async () => {
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
|
||||
await Factory.sleep(1)
|
||||
await Factory.sleep(2)
|
||||
|
||||
const setting = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_LIMIT')
|
||||
expect(setting).to.be.a('string')
|
||||
@@ -137,11 +144,18 @@ describe('settings service', function () {
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionId: subscriptionId,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
@@ -150,37 +164,41 @@ describe('settings service', function () {
|
||||
|
||||
await Files.uploadFile(application.fileService, buffer, 'my-file', 'md', 1000)
|
||||
|
||||
await Factory.publishMockedEvent('FILE_UPLOADED', {
|
||||
userUuid: user.uuid,
|
||||
fileByteSize: 123,
|
||||
filePath: 'foobar',
|
||||
fileName: 'barbuzz',
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
const limitSettingBefore = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_LIMIT')
|
||||
expect(limitSettingBefore).to.equal('107374182400')
|
||||
|
||||
const usedSettingBefore = await application.settings.getSubscriptionSetting('FILE_UPLOAD_BYTES_USED')
|
||||
expect(usedSettingBefore).to.equal('123')
|
||||
|
||||
expect(usedSettingBefore).to.equal('196')
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_EXPIRED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userExistingSubscriptionsCount: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 2,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: false,
|
||||
totalActiveSubscriptionsCount: 2,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(1)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('subscriptions', function () {
|
||||
let application
|
||||
let context
|
||||
let subscriptionManager
|
||||
let subscriptionId = 3001
|
||||
|
||||
afterEach(async function () {
|
||||
await Factory.safeDeinit(application)
|
||||
@@ -32,13 +33,20 @@ describe('subscriptions', function () {
|
||||
|
||||
await Factory.publishMockedEvent('SUBSCRIPTION_PURCHASED', {
|
||||
userEmail: context.email,
|
||||
subscriptionId: 1,
|
||||
subscriptionId: subscriptionId++,
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||
timestamp: Date.now(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
userRegisteredAt: 1,
|
||||
billingFrequency: 12,
|
||||
payAmount: 59.00
|
||||
})
|
||||
await Factory.sleep(0.25)
|
||||
await Factory.sleep(2)
|
||||
})
|
||||
|
||||
it('should invite a user by email to a shared subscription', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.151.9",
|
||||
"version": "2.155.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
## [1.3.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/toast",
|
||||
"version": "1.3.11",
|
||||
"version": "1.3.12",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.17.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-25)
|
||||
|
||||
### Features
|
||||
|
||||
* Markdown, HTML & JSON export options for super notes ([#2054](https://github.com/standardnotes/app/issues/2054)) ([dcc8cfb](https://github.com/standardnotes/app/commit/dcc8cfbe45644dbb77e0ade0073017e6ac66164c))
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-24)
|
||||
|
||||
### Features
|
||||
|
||||
* New 'What's New' section in Preferences ([#2049](https://github.com/standardnotes/app/issues/2049)) (skip e2e) ([0a01ddb](https://github.com/standardnotes/app/commit/0a01ddb430154fa4518985458c15c644855c80ea))
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* option to show markdown preview for super notes (skip e2e) ([#2048](https://github.com/standardnotes/app/issues/2048)) ([8579ff3](https://github.com/standardnotes/app/commit/8579ff39b1f81dbbcf22b4031ede183ab4287262))
|
||||
|
||||
## [1.14.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.14.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.14.3",
|
||||
"version": "1.17.8",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export type ChangelogVersion = {
|
||||
version: string | null
|
||||
title: string
|
||||
date: string | null
|
||||
body: string
|
||||
parsed: Record<string, string[]>
|
||||
}
|
||||
|
||||
export interface Changelog {
|
||||
title: string
|
||||
description: string
|
||||
versions: Array<ChangelogVersion>
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Environment } from '@standardnotes/models'
|
||||
import { StorageServiceInterface, StorageKey } from '@standardnotes/services'
|
||||
import { Changelog, ChangelogVersion } from './Changelog'
|
||||
import { ChangelogServiceInterface } from './ChangelogServiceInterface'
|
||||
import { LegacyWebToDesktopVersionMapping } from './LegacyDesktopMapping'
|
||||
import { LegacyWebToMobileVersionMapping } from './LegacyMobileMapping'
|
||||
|
||||
const WebChangelogUrl = 'https://raw.githubusercontent.com/standardnotes/app/main/packages/web/CHANGELOG.md.json'
|
||||
const DesktopDownloadsUrlBase = 'https://github.com/standardnotes/app/releases/tag/%40standardnotes%2Fdesktop%40'
|
||||
|
||||
export class ChangelogService implements ChangelogServiceInterface {
|
||||
private changeLog?: Changelog
|
||||
|
||||
constructor(private environment: Environment, private diskService: StorageServiceInterface) {}
|
||||
|
||||
private async performDownloadChangelog(): Promise<Changelog> {
|
||||
const response = await fetch(WebChangelogUrl)
|
||||
const changelog = await response.text()
|
||||
const parsedData = JSON.parse(changelog)
|
||||
return parsedData
|
||||
}
|
||||
|
||||
public async getChangelog(): Promise<Changelog> {
|
||||
if (this.changeLog) {
|
||||
return this.changeLog
|
||||
}
|
||||
|
||||
this.changeLog = await this.performDownloadChangelog()
|
||||
|
||||
if (this.environment !== Environment.Web) {
|
||||
const legacyMapping = this.getLegacyMapping()
|
||||
|
||||
this.changeLog.versions = this.changeLog.versions.map((versionRecord) => {
|
||||
const versionString = versionRecord.version || ''
|
||||
return {
|
||||
...versionRecord,
|
||||
version: legacyMapping[versionString] || versionRecord.version,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this.changeLog
|
||||
}
|
||||
|
||||
public markAsRead(): void {
|
||||
if (!this.changeLog) {
|
||||
return
|
||||
}
|
||||
|
||||
this.diskService.setValue(StorageKey.LastReadChangelogVersion, this.changeLog.versions[0].version)
|
||||
}
|
||||
|
||||
public getLastReadVersion(): string | undefined {
|
||||
return this.diskService.getValue(StorageKey.LastReadChangelogVersion)
|
||||
}
|
||||
|
||||
public async getVersions(): Promise<ChangelogVersion[]> {
|
||||
const changelog = await this.getChangelog()
|
||||
|
||||
return changelog.versions
|
||||
}
|
||||
|
||||
private getLegacyMapping(): Record<string, string> {
|
||||
return this.environment === Environment.Desktop
|
||||
? LegacyWebToDesktopVersionMapping
|
||||
: this.environment === Environment.Mobile
|
||||
? LegacyWebToMobileVersionMapping
|
||||
: {}
|
||||
}
|
||||
|
||||
public getDesktopDownloadsUrl(version: string): string {
|
||||
return DesktopDownloadsUrlBase + version
|
||||
}
|
||||
|
||||
public getDesktopVersionForWebVersion(webVersion: string): string {
|
||||
return LegacyWebToDesktopVersionMapping[webVersion] ?? webVersion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Changelog, ChangelogVersion } from './Changelog'
|
||||
|
||||
export interface ChangelogServiceInterface {
|
||||
getChangelog(): Promise<Changelog>
|
||||
getVersions(): Promise<ChangelogVersion[]>
|
||||
getDesktopDownloadsUrl(version: string): string
|
||||
getDesktopVersionForWebVersion(webVersion: string): string
|
||||
markAsRead(): void
|
||||
getLastReadVersion(): string | undefined
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* Before 11/23/2022, web and desktop used independent semantic versioning. After this point, desktop automatically
|
||||
* uses web's versioning. The mapping below allows us to see what particular web version a legacy desktop version maps to
|
||||
*/
|
||||
export const LegacyWebToDesktopVersionMapping: Record<string, string> = {
|
||||
'3.107.0': '3.101.2',
|
||||
'3.106.0': '3.101.1',
|
||||
'3.105.0': '3.101.0',
|
||||
'3.104.1': '3.100.18',
|
||||
'3.104.0': '3.100.17',
|
||||
'3.103.2': '3.100.16',
|
||||
'3.103.1': '3.100.15',
|
||||
'3.103.0': '3.100.14',
|
||||
'3.102.0': '3.100.13',
|
||||
'3.101.2': '3.100.12',
|
||||
'3.101.1': '3.100.11',
|
||||
'3.101.0': '3.100.10',
|
||||
'3.100.8': '3.100.9',
|
||||
'3.100.7': '3.100.8',
|
||||
'3.100.6': '3.100.7',
|
||||
'3.100.5': '3.100.6',
|
||||
'3.100.4': '3.100.5',
|
||||
'3.100.3': '3.100.4',
|
||||
'3.100.2': '3.100.3',
|
||||
'3.100.1': '3.100.2',
|
||||
'3.100.0': '3.100.1',
|
||||
'3.99.0': '3.100.0',
|
||||
'3.98.2': '3.23.301',
|
||||
'3.98.1': '3.23.300',
|
||||
'3.98.0': '3.23.299',
|
||||
'3.97.0': '3.23.298',
|
||||
'3.96.1': '3.23.297',
|
||||
'3.96.0': '3.23.296',
|
||||
'3.95.1': '3.23.295',
|
||||
'3.95.0': '3.23.294',
|
||||
'3.94.2': '3.23.293',
|
||||
'3.94.1': '3.23.292',
|
||||
'3.94.0': '3.23.291',
|
||||
'3.93.19': '3.23.290',
|
||||
'3.93.18': '3.23.289',
|
||||
'3.93.17': '3.23.288',
|
||||
'3.93.16': '3.23.287',
|
||||
'3.93.15': '3.23.286',
|
||||
'3.93.14': '3.23.285',
|
||||
'3.93.13': '3.23.284',
|
||||
'3.93.12': '3.23.283',
|
||||
'3.93.11': '3.23.282',
|
||||
'3.93.10': '3.23.281',
|
||||
'3.93.9': '3.23.280',
|
||||
'3.93.8': '3.23.279',
|
||||
'3.93.7': '3.23.278',
|
||||
'3.93.6': '3.23.277',
|
||||
'3.93.5': '3.23.276',
|
||||
'3.93.4': '3.23.275',
|
||||
'3.93.3': '3.23.274',
|
||||
'3.93.2': '3.23.273',
|
||||
'3.93.1': '3.23.272',
|
||||
'3.93.0': '3.23.271',
|
||||
'3.92.0': '3.23.270',
|
||||
'3.91.1': '3.23.269',
|
||||
'3.91.0': '3.23.268',
|
||||
'3.90.11': '3.23.267',
|
||||
'3.90.10': '3.23.266',
|
||||
'3.90.9': '3.23.265',
|
||||
'3.90.8': '3.23.264',
|
||||
'3.90.7': '3.23.263',
|
||||
'3.90.6': '3.23.262',
|
||||
'3.90.5': '3.23.261',
|
||||
'3.90.4': '3.23.260',
|
||||
'3.90.3': '3.23.259',
|
||||
'3.90.2': '3.23.258',
|
||||
'3.90.1': '3.23.257',
|
||||
'3.90.0': '3.23.256',
|
||||
'3.89.0': '3.23.255',
|
||||
'3.88.1': '3.23.254',
|
||||
'3.88.0': '3.23.253',
|
||||
'3.87.2': '3.23.252',
|
||||
'3.87.1': '3.23.251',
|
||||
'3.87.0': '3.23.250',
|
||||
'3.86.0': '3.23.249',
|
||||
'3.85.2': '3.23.248',
|
||||
'3.85.1': '3.23.247',
|
||||
'3.85.0': '3.23.246',
|
||||
'3.84.7': '3.23.245',
|
||||
'3.84.6': '3.23.244',
|
||||
'3.84.5': '3.23.243',
|
||||
'3.84.4': '3.23.242',
|
||||
'3.84.3': '3.23.241',
|
||||
'3.84.2': '3.23.240',
|
||||
'3.84.1': '3.23.239',
|
||||
'3.84.0': '3.23.238',
|
||||
'3.83.0': '3.23.237',
|
||||
'3.82.6': '3.23.236',
|
||||
'3.82.5': '3.23.235',
|
||||
'3.82.4': '3.23.234',
|
||||
'3.82.3': '3.23.233',
|
||||
'3.82.2': '3.23.232',
|
||||
'3.82.1': '3.23.231',
|
||||
'3.82.0': '3.23.230',
|
||||
'3.81.0': '3.23.229',
|
||||
'3.80.1': '3.23.228',
|
||||
'3.80.0': '3.23.227',
|
||||
'3.79.0': '3.23.226',
|
||||
'3.78.1': '3.23.225',
|
||||
'3.78.0': '3.23.224',
|
||||
'3.77.1': '3.23.223',
|
||||
'3.77.0': '3.23.222',
|
||||
'3.76.2': '3.23.221',
|
||||
'3.76.1': '3.23.220',
|
||||
'3.76.0': '3.23.219',
|
||||
'3.75.1': '3.23.218',
|
||||
'3.75.0': '3.23.217',
|
||||
'3.74.0': '3.23.216',
|
||||
'3.73.0': '3.23.215',
|
||||
'3.72.4': '3.23.214',
|
||||
'3.72.3': '3.23.213',
|
||||
'3.72.2': '3.23.212',
|
||||
'3.72.1': '3.23.211',
|
||||
'3.72.0': '3.23.210',
|
||||
'3.71.8': '3.23.209',
|
||||
'3.71.7': '3.23.208',
|
||||
'3.71.6': '3.23.207',
|
||||
'3.71.5': '3.23.206',
|
||||
'3.71.4': '3.23.205',
|
||||
'3.71.3': '3.23.204',
|
||||
'3.71.2': '3.23.203',
|
||||
'3.71.1': '3.23.202',
|
||||
'3.71.0': '3.23.201',
|
||||
'3.70.5': '3.23.200',
|
||||
'3.70.4': '3.23.199',
|
||||
'3.70.3': '3.23.198',
|
||||
'3.70.2': '3.23.197',
|
||||
'3.70.1': '3.23.196',
|
||||
'3.70.0': '3.23.195',
|
||||
'3.69.1': '3.23.194',
|
||||
'3.69.0': '3.23.193',
|
||||
'3.68.6': '3.23.192',
|
||||
'3.68.5': '3.23.191',
|
||||
'3.68.4': '3.23.190',
|
||||
'3.68.3': '3.23.189',
|
||||
'3.68.2': '3.23.188',
|
||||
'3.68.1': '3.23.187',
|
||||
'3.68.0': '3.23.186',
|
||||
'3.67.4': '3.23.185',
|
||||
'3.67.3': '3.23.184',
|
||||
'3.67.2': '3.23.183',
|
||||
'3.67.1': '3.23.182',
|
||||
'3.67.0': '3.23.181',
|
||||
'3.66.0': '3.23.180',
|
||||
'3.65.0': '3.23.179',
|
||||
'3.64.0': '3.23.178',
|
||||
'3.63.3': '3.23.177',
|
||||
'3.63.2': '3.23.176',
|
||||
'3.63.1': '3.23.175',
|
||||
'3.63.0': '3.23.174',
|
||||
'3.62.4': '3.23.173',
|
||||
'3.62.3': '3.23.172',
|
||||
'3.62.2': '3.23.171',
|
||||
'3.62.1': '3.23.170',
|
||||
'3.62.0': '3.23.169',
|
||||
'3.61.0': '3.23.168',
|
||||
'3.60.0': '3.23.167',
|
||||
'3.59.3': '3.23.166',
|
||||
'3.59.2': '3.23.165',
|
||||
'3.59.1': '3.23.164',
|
||||
'3.59.0': '3.23.163',
|
||||
'3.58.1': '3.23.162',
|
||||
'3.58.0': '3.23.161',
|
||||
'3.57.3': '3.23.160',
|
||||
'3.57.2': '3.23.159',
|
||||
'3.57.1': '3.23.158',
|
||||
'3.57.0': '3.23.157',
|
||||
'3.56.0': '3.23.156',
|
||||
'3.55.2': '3.23.155',
|
||||
'3.55.1': '3.23.154',
|
||||
'3.55.0': '3.23.153',
|
||||
'3.54.6': '3.23.152',
|
||||
'3.54.5': '3.23.151',
|
||||
'3.54.4': '3.23.150',
|
||||
'3.54.3': '3.23.149',
|
||||
'3.54.2': '3.23.148',
|
||||
'3.54.1': '3.23.147',
|
||||
'3.54.0': '3.23.146',
|
||||
'3.53.0': '3.23.145',
|
||||
'3.52.1': '3.23.144',
|
||||
'3.52.0': '3.23.143',
|
||||
'3.51.0': '3.23.142',
|
||||
'3.50.6': '3.23.141',
|
||||
'3.50.5': '3.23.140',
|
||||
'3.50.4': '3.23.139',
|
||||
'3.50.3': '3.23.138',
|
||||
'3.50.2': '3.23.137',
|
||||
'3.50.1': '3.23.136',
|
||||
'3.50.0': '3.23.135',
|
||||
'3.49.3': '3.23.134',
|
||||
'3.49.2': '3.23.133',
|
||||
'3.49.1': '3.23.132',
|
||||
'3.49.0': '3.23.131',
|
||||
'3.48.3': '3.23.130',
|
||||
'3.48.2': '3.23.129',
|
||||
'3.48.1': '3.23.128',
|
||||
'3.48.0': '3.23.127',
|
||||
'3.47.3': '3.23.126',
|
||||
'3.47.2': '3.23.125',
|
||||
'3.47.1': '3.23.124',
|
||||
'3.47.0': '3.23.123',
|
||||
'3.46.3': '3.23.122',
|
||||
'3.46.2': '3.23.121',
|
||||
'3.46.1': '3.23.120',
|
||||
'3.46.0': '3.23.119',
|
||||
'3.45.18': '3.23.118',
|
||||
'3.45.17': '3.23.117',
|
||||
'3.45.16': '3.23.116',
|
||||
'3.45.15': '3.23.115',
|
||||
'3.45.14': '3.23.114',
|
||||
'3.45.13': '3.23.113',
|
||||
'3.45.12': '3.23.112',
|
||||
'3.45.11': '3.23.111',
|
||||
'3.45.10': '3.23.110',
|
||||
'3.45.9': '3.23.109',
|
||||
'3.45.8': '3.23.108',
|
||||
'3.45.7': '3.23.107',
|
||||
'3.45.6': '3.23.106',
|
||||
'3.45.5': '3.23.105',
|
||||
'3.45.4': '3.23.104',
|
||||
'3.45.3': '3.23.103',
|
||||
'3.45.2': '3.23.102',
|
||||
'3.45.1': '3.23.101',
|
||||
'3.45.0': '3.23.100',
|
||||
'3.44.7': '3.23.99',
|
||||
'3.44.6': '3.23.98',
|
||||
'3.44.5': '3.23.97',
|
||||
'3.44.4': '3.23.96',
|
||||
'3.44.3': '3.23.95',
|
||||
'3.44.2': '3.23.94',
|
||||
'3.44.1': '3.23.93',
|
||||
'3.44.0': '3.23.92',
|
||||
'3.43.1': '3.23.91',
|
||||
'3.43.0': '3.23.90',
|
||||
'3.42.1': '3.23.89',
|
||||
'3.42.0': '3.23.88',
|
||||
'3.41.3': '3.23.87',
|
||||
'3.41.2': '3.23.86',
|
||||
'3.41.1': '3.23.85',
|
||||
'3.41.0': '3.23.84',
|
||||
'3.40.7': '3.23.83',
|
||||
'3.40.6': '3.23.82',
|
||||
'3.40.5': '3.23.81',
|
||||
'3.40.4': '3.23.80',
|
||||
'3.40.3': '3.23.79',
|
||||
'3.40.2': '3.23.78',
|
||||
'3.40.1': '3.23.77',
|
||||
'3.40.0': '3.23.76',
|
||||
'3.39.1': '3.23.75',
|
||||
'3.39.0': '3.23.74',
|
||||
'3.38.6': '3.23.73',
|
||||
'3.38.5': '3.23.72',
|
||||
'3.38.4': '3.23.71',
|
||||
'3.38.3': '3.23.70',
|
||||
'3.38.2': '3.23.69',
|
||||
'3.38.1': '3.23.68',
|
||||
'3.38.0': '3.23.67',
|
||||
'3.37.7': '3.23.66',
|
||||
'3.37.6': '3.23.65',
|
||||
'3.37.5': '3.23.64',
|
||||
'3.37.4': '3.23.63',
|
||||
'3.37.3': '3.23.62',
|
||||
'3.37.2': '3.23.61',
|
||||
'3.37.1': '3.23.60',
|
||||
'3.37.0': '3.23.59',
|
||||
'3.36.0': '3.23.58',
|
||||
'3.35.2': '3.23.57',
|
||||
'3.35.1': '3.23.56',
|
||||
'3.35.0': '3.23.55',
|
||||
'3.34.0': '3.23.54',
|
||||
'3.33.8': '3.23.53',
|
||||
'3.33.7': '3.23.52',
|
||||
'3.33.6': '3.23.51',
|
||||
'3.33.5': '3.23.50',
|
||||
'3.33.4': '3.23.49',
|
||||
'3.33.3': '3.23.48',
|
||||
'3.33.2': '3.23.47',
|
||||
'3.33.1': '3.23.46',
|
||||
'3.33.0': '3.23.45',
|
||||
'3.32.0': '3.23.44',
|
||||
'3.31.1': '3.23.43',
|
||||
'3.31.0': '3.23.42',
|
||||
'3.30.0': '3.23.41',
|
||||
'3.29.2': '3.23.40',
|
||||
'3.29.1': '3.23.39',
|
||||
'3.29.0': '3.23.38',
|
||||
'3.28.1': '3.23.37',
|
||||
'3.28.0': '3.23.36',
|
||||
'3.27.7': '3.23.35',
|
||||
'3.27.6': '3.23.34',
|
||||
'3.27.5': '3.23.33',
|
||||
'3.27.4': '3.23.32',
|
||||
'3.27.3': '3.23.31',
|
||||
'3.27.2': '3.23.30',
|
||||
'3.27.1': '3.23.29',
|
||||
'3.27.0': '3.23.28',
|
||||
'3.26.4': '3.23.27',
|
||||
'3.26.3': '3.23.26',
|
||||
'3.26.2': '3.23.25',
|
||||
'3.26.1': '3.23.24',
|
||||
'3.26.0': '3.23.23',
|
||||
'3.25.0': '3.23.22',
|
||||
'3.24.6': '3.23.21',
|
||||
'3.24.5': '3.23.20',
|
||||
'3.24.4': '3.23.19',
|
||||
'3.24.3': '3.23.18',
|
||||
'3.24.3-alpha.1': '3.23.17',
|
||||
'3.24.3-alpha.0': '3.23.16',
|
||||
'3.24.2': '3.23.15',
|
||||
'3.24.2-alpha.1': '3.23.14',
|
||||
'3.24.2-alpha.0': '3.23.13',
|
||||
'3.24.1': '3.23.12',
|
||||
'3.24.1-alpha.0': '3.23.11',
|
||||
'3.24.0': '3.23.10',
|
||||
'3.24.0-alpha.7': '3.23.9',
|
||||
'3.24.0-alpha.6': '3.23.8',
|
||||
'3.24.0-alpha.5': '3.23.7',
|
||||
'3.24.0-alpha.4': '3.23.6',
|
||||
'3.24.0-alpha.3': '3.23.5',
|
||||
'3.24.0-alpha.2': '3.23.4',
|
||||
'3.24.0-alpha.1': '3.23.3',
|
||||
'3.24.0-alpha.0': '3.23.2',
|
||||
'3.23.0': '3.23.1',
|
||||
'3.23.0-alpha.1': '3.23.0',
|
||||
'3.23.0-alpha.0': '3.22.21',
|
||||
'3.22.7-alpha.0': '3.22.20',
|
||||
'3.22.6': '3.22.19',
|
||||
'3.22.6-alpha.0': '3.22.18',
|
||||
'3.22.5': '3.22.17',
|
||||
'3.22.4': '3.22.13-alpha.9',
|
||||
'3.22.3': '3.22.13-alpha.8',
|
||||
'3.22.3-alpha.9': '3.22.16-alpha.1',
|
||||
'3.22.3-alpha.8': '3.22.16-alpha.0',
|
||||
'3.22.3-alpha.7': '3.22.15',
|
||||
'3.22.3-alpha.6': '3.22.15-alpha.3',
|
||||
'3.22.3-alpha.5': '3.22.15-alpha.2',
|
||||
'3.22.3-alpha.4': '3.22.15-alpha.1',
|
||||
'3.22.3-alpha.3': '3.22.15-alpha.0',
|
||||
'3.22.3-alpha.2': '3.22.14',
|
||||
'3.22.3-alpha.1': '3.22.14-alpha.0',
|
||||
'3.22.3-alpha.0': '3.22.13',
|
||||
'3.22.2': '3.22.13-alpha.10',
|
||||
'3.22.1': '3.22.13-alpha.7',
|
||||
'3.22.0': '3.22.13-alpha.6',
|
||||
'3.21.0': '3.22.13-alpha.5',
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* Before 11/23/2022, web and mobile used independent semantic versioning. After this point, mobile automatically
|
||||
* uses web's versioning. The mapping below allows us to see what particular web version a legacy mobile version maps to
|
||||
*/
|
||||
export const LegacyWebToMobileVersionMapping: Record<string, string> = {
|
||||
'3.107.0': '3.46.31',
|
||||
'3.106.0': '3.46.30',
|
||||
'3.105.0': '3.46.29',
|
||||
'3.104.1': '3.46.28',
|
||||
'3.104.0': '3.46.27',
|
||||
'3.103.2': '3.46.26',
|
||||
'3.103.1': '3.46.25',
|
||||
'3.103.0': '3.46.24',
|
||||
'3.102.0': '3.46.23',
|
||||
'3.101.2': '3.46.22',
|
||||
'3.101.1': '3.46.21',
|
||||
'3.101.0': '3.46.20',
|
||||
'3.100.8': '3.46.19',
|
||||
'3.100.7': '3.46.18',
|
||||
'3.100.6': '3.46.17',
|
||||
'3.100.5': '3.46.16',
|
||||
'3.100.4': '3.46.15',
|
||||
'3.100.3': '3.46.14',
|
||||
'3.100.2': '3.46.13',
|
||||
'3.100.1': '3.46.12',
|
||||
'3.100.0': '3.46.11',
|
||||
'3.99.0': '3.46.10',
|
||||
'3.98.2': '3.46.9',
|
||||
'3.98.1': '3.46.8',
|
||||
'3.98.0': '3.46.7',
|
||||
'3.97.0': '3.46.6',
|
||||
'3.96.1': '3.46.5',
|
||||
'3.96.0': '3.46.4',
|
||||
'3.95.1': '3.46.3',
|
||||
'3.95.0': '3.46.2',
|
||||
'3.94.2': '3.46.1',
|
||||
'3.94.1': '3.46.0',
|
||||
'3.94.0': '3.45.42',
|
||||
'3.93.19': '3.45.41',
|
||||
'3.93.18': '3.45.40',
|
||||
'3.93.17': '3.45.39',
|
||||
'3.93.16': '3.45.38',
|
||||
'3.93.15': '3.45.37',
|
||||
'3.93.14': '3.45.36',
|
||||
'3.93.13': '3.45.35',
|
||||
'3.93.12': '3.45.34',
|
||||
'3.93.11': '3.45.33',
|
||||
'3.93.10': '3.45.32',
|
||||
'3.93.9': '3.45.31',
|
||||
'3.93.8': '3.45.30',
|
||||
'3.93.7': '3.45.29',
|
||||
'3.93.6': '3.45.28',
|
||||
'3.93.5': '3.45.27',
|
||||
'3.93.4': '3.45.26',
|
||||
'3.93.3': '3.45.25',
|
||||
'3.93.2': '3.45.24',
|
||||
'3.93.1': '3.45.23',
|
||||
'3.93.0': '3.45.22',
|
||||
'3.92.0': '3.45.21',
|
||||
'3.91.1': '3.45.20',
|
||||
'3.91.0': '3.45.19',
|
||||
'3.90.11': '3.45.18',
|
||||
'3.90.10': '3.45.17',
|
||||
'3.90.9': '3.45.16',
|
||||
'3.90.8': '3.45.15',
|
||||
'3.90.7': '3.45.14',
|
||||
'3.90.6': '3.45.13',
|
||||
'3.90.5': '3.45.12',
|
||||
'3.90.4': '3.45.11',
|
||||
'3.90.3': '3.45.10',
|
||||
'3.90.2': '3.45.9',
|
||||
'3.90.1': '3.45.8',
|
||||
'3.90.0': '3.45.7',
|
||||
'3.89.0': '3.45.6',
|
||||
'3.88.1': '3.45.5',
|
||||
'3.88.0': '3.45.4',
|
||||
'3.87.2': '3.45.3',
|
||||
'3.87.1': '3.45.2',
|
||||
'3.87.0': '3.45.1',
|
||||
'3.86.0': '3.45.0',
|
||||
'3.85.2': '3.44.5',
|
||||
'3.85.1': '3.44.4',
|
||||
'3.85.0': '3.44.3',
|
||||
'3.84.7': '3.44.2',
|
||||
'3.84.6': '3.44.1',
|
||||
'3.84.5': '3.44.0',
|
||||
'3.84.4': '3.43.33',
|
||||
'3.84.3': '3.43.32',
|
||||
'3.84.2': '3.43.31',
|
||||
'3.84.1': '3.43.30',
|
||||
'3.84.0': '3.43.29',
|
||||
'3.83.0': '3.43.28',
|
||||
'3.82.6': '3.43.27',
|
||||
'3.82.5': '3.43.26',
|
||||
'3.82.4': '3.43.25',
|
||||
'3.82.3': '3.43.24',
|
||||
'3.82.2': '3.43.23',
|
||||
'3.82.1': '3.43.22',
|
||||
'3.82.0': '3.43.21',
|
||||
'3.81.0': '3.43.20',
|
||||
'3.80.1': '3.43.19',
|
||||
'3.80.0': '3.43.18',
|
||||
'3.79.0': '3.43.17',
|
||||
'3.78.1': '3.43.16',
|
||||
'3.78.0': '3.43.15',
|
||||
'3.77.1': '3.43.14',
|
||||
'3.77.0': '3.43.13',
|
||||
'3.76.2': '3.43.12',
|
||||
'3.76.1': '3.43.11',
|
||||
'3.76.0': '3.43.10',
|
||||
'3.75.1': '3.43.9',
|
||||
'3.75.0': '3.43.8',
|
||||
'3.74.0': '3.43.7',
|
||||
'3.73.0': '3.43.6',
|
||||
'3.72.4': '3.43.5',
|
||||
'3.72.3': '3.43.4',
|
||||
'3.72.2': '3.43.3',
|
||||
'3.72.1': '3.43.2',
|
||||
'3.72.0': '3.43.1',
|
||||
'3.71.8': '3.43.0',
|
||||
'3.71.7': '3.42.5',
|
||||
'3.71.6': '3.42.4',
|
||||
'3.71.5': '3.42.3',
|
||||
'3.71.4': '3.42.2',
|
||||
'3.71.3': '3.42.1',
|
||||
'3.71.2': '3.42.0',
|
||||
'3.71.1': '3.41.10',
|
||||
'3.71.0': '3.41.9',
|
||||
'3.70.5': '3.41.8',
|
||||
'3.70.4': '3.41.7',
|
||||
'3.70.3': '3.41.6',
|
||||
'3.70.2': '3.41.5',
|
||||
'3.70.1': '3.41.4',
|
||||
'3.70.0': '3.41.3',
|
||||
'3.69.1': '3.41.2',
|
||||
'3.69.0': '3.41.1',
|
||||
'3.68.6': '3.41.0',
|
||||
'3.68.5': '3.40.2',
|
||||
'3.68.4': '3.40.1',
|
||||
'3.68.3': '3.40.0',
|
||||
'3.68.2': '3.39.12',
|
||||
'3.68.1': '3.39.11',
|
||||
'3.68.0': '3.39.10',
|
||||
'3.67.4': '3.39.9',
|
||||
'3.67.3': '3.39.8',
|
||||
'3.67.2': '3.39.7',
|
||||
'3.67.1': '3.39.6',
|
||||
'3.67.0': '3.39.5',
|
||||
'3.66.0': '3.39.4',
|
||||
'3.65.0': '3.39.3',
|
||||
'3.64.0': '3.39.2',
|
||||
'3.63.3': '3.39.1',
|
||||
'3.63.2': '3.39.0',
|
||||
'3.63.1': '3.38.1',
|
||||
'3.63.0': '3.38.0',
|
||||
'3.62.4': '3.37.14',
|
||||
'3.62.3': '3.37.13',
|
||||
'3.62.2': '3.37.12',
|
||||
'3.62.1': '3.37.11',
|
||||
'3.62.0': '3.37.10',
|
||||
'3.61.0': '3.37.9',
|
||||
'3.60.0': '3.37.8',
|
||||
'3.59.3': '3.37.7',
|
||||
'3.59.2': '3.37.6',
|
||||
'3.59.1': '3.37.5',
|
||||
'3.59.0': '3.37.4',
|
||||
'3.58.1': '3.37.3',
|
||||
'3.58.0': '3.37.2',
|
||||
'3.57.3': '3.37.1',
|
||||
'3.57.2': '3.37.0',
|
||||
'3.57.1': '3.36.18',
|
||||
'3.57.0': '3.36.17',
|
||||
'3.56.0': '3.36.16',
|
||||
'3.55.2': '3.36.15',
|
||||
'3.55.1': '3.36.14',
|
||||
'3.55.0': '3.36.13',
|
||||
'3.54.6': '3.36.12',
|
||||
'3.54.5': '3.36.11',
|
||||
'3.54.4': '3.36.10',
|
||||
'3.54.3': '3.36.9',
|
||||
'3.54.2': '3.36.8',
|
||||
'3.54.1': '3.36.7',
|
||||
'3.54.0': '3.36.6',
|
||||
'3.53.0': '3.36.5',
|
||||
'3.52.1': '3.36.4',
|
||||
'3.52.0': '3.36.3',
|
||||
'3.51.0': '3.36.2',
|
||||
'3.50.6': '3.36.1',
|
||||
'3.50.5': '3.36.0',
|
||||
'3.50.4': '3.35.16',
|
||||
'3.50.3': '3.35.15',
|
||||
'3.50.2': '3.35.14',
|
||||
'3.50.1': '3.35.13',
|
||||
'3.50.0': '3.35.12',
|
||||
'3.49.3': '3.35.11',
|
||||
'3.49.2': '3.35.10',
|
||||
'3.49.1': '3.35.9',
|
||||
'3.49.0': '3.35.8',
|
||||
'3.48.3': '3.35.7',
|
||||
'3.48.2': '3.35.6',
|
||||
'3.48.1': '3.35.5',
|
||||
'3.48.0': '3.35.4',
|
||||
'3.47.3': '3.35.3',
|
||||
'3.47.2': '3.35.2',
|
||||
'3.47.1': '3.35.1',
|
||||
'3.47.0': '3.35.0',
|
||||
'3.46.3': '3.34.3',
|
||||
'3.46.2': '3.34.2',
|
||||
'3.46.1': '3.34.1',
|
||||
'3.46.0': '3.34.0',
|
||||
'3.45.18': '3.33.7',
|
||||
'3.45.17': '3.33.6',
|
||||
'3.45.16': '3.33.5',
|
||||
'3.45.15': '3.33.4',
|
||||
'3.45.14': '3.33.3',
|
||||
'3.45.13': '3.33.2',
|
||||
'3.45.12': '3.33.1',
|
||||
'3.45.11': '3.33.0',
|
||||
'3.45.10': '3.32.5',
|
||||
'3.45.9': '3.32.4',
|
||||
'3.45.8': '3.32.3',
|
||||
'3.45.7': '3.32.2',
|
||||
'3.45.6': '3.32.1',
|
||||
'3.45.5': '3.32.0',
|
||||
'3.45.4': '3.31.29',
|
||||
'3.45.3': '3.31.28',
|
||||
'3.45.2': '3.31.27',
|
||||
'3.45.1': '3.31.26',
|
||||
'3.45.0': '3.31.25',
|
||||
'3.44.7': '3.31.24',
|
||||
'3.44.6': '3.31.23',
|
||||
'3.44.5': '3.31.22',
|
||||
'3.44.4': '3.31.21',
|
||||
'3.44.3': '3.31.20',
|
||||
'3.44.2': '3.31.19',
|
||||
'3.44.1': '3.31.18',
|
||||
'3.44.0': '3.31.17',
|
||||
'3.43.1': '3.31.16',
|
||||
'3.43.0': '3.31.15',
|
||||
'3.42.1': '3.31.14',
|
||||
'3.42.0': '3.31.13',
|
||||
'3.41.3': '3.31.12',
|
||||
'3.41.2': '3.31.11',
|
||||
'3.41.1': '3.31.10',
|
||||
'3.41.0': '3.31.9',
|
||||
'3.40.7': '3.31.8',
|
||||
'3.40.6': '3.31.7',
|
||||
'3.40.5': '3.31.6',
|
||||
'3.40.4': '3.31.5',
|
||||
'3.40.3': '3.31.4',
|
||||
'3.40.2': '3.31.3',
|
||||
'3.40.1': '3.31.2',
|
||||
'3.40.0': '3.31.1',
|
||||
'3.39.1': '3.31.0',
|
||||
'3.39.0': '3.30.38',
|
||||
'3.38.6': '3.30.37',
|
||||
'3.38.5': '3.30.36',
|
||||
'3.38.4': '3.30.35',
|
||||
'3.38.3': '3.30.34',
|
||||
'3.38.2': '3.30.33',
|
||||
'3.38.1': '3.30.32',
|
||||
'3.38.0': '3.30.31',
|
||||
'3.37.7': '3.30.30',
|
||||
'3.37.6': '3.30.29',
|
||||
'3.37.5': '3.30.28',
|
||||
'3.37.4': '3.30.27',
|
||||
'3.37.3': '3.30.26',
|
||||
'3.37.2': '3.30.25',
|
||||
'3.37.1': '3.30.24',
|
||||
'3.37.0': '3.30.23',
|
||||
'3.36.0': '3.30.22',
|
||||
'3.35.2': '3.30.21',
|
||||
'3.35.1': '3.30.20',
|
||||
'3.35.0': '3.30.19',
|
||||
'3.34.0': '3.30.18',
|
||||
'3.33.8': '3.30.17',
|
||||
'3.33.7': '3.30.16',
|
||||
'3.33.6': '3.30.15',
|
||||
'3.33.5': '3.30.14',
|
||||
'3.33.4': '3.30.13',
|
||||
'3.33.3': '3.30.12',
|
||||
'3.33.2': '3.30.11',
|
||||
'3.33.1': '3.30.10',
|
||||
'3.33.0': '3.30.9',
|
||||
'3.32.0': '3.30.8',
|
||||
'3.31.1': '3.30.7',
|
||||
'3.31.0': '3.30.6',
|
||||
'3.30.0': '3.30.5',
|
||||
'3.29.2': '3.30.4',
|
||||
'3.29.1': '3.30.3',
|
||||
'3.29.0': '3.30.2',
|
||||
'3.28.1': '3.30.1',
|
||||
'3.28.0': '3.30.0',
|
||||
'3.27.7': '3.29.0',
|
||||
'3.27.6': '3.28.3',
|
||||
'3.27.5': '3.28.2',
|
||||
'3.27.4': '3.28.1',
|
||||
'3.27.3': '3.28.0',
|
||||
'3.27.2': '3.27.13',
|
||||
'3.27.1': '3.27.12',
|
||||
'3.27.0': '3.27.11',
|
||||
'3.26.4': '3.27.10',
|
||||
'3.26.3': '3.27.9',
|
||||
'3.26.2': '3.27.8',
|
||||
'3.26.1': '3.27.7',
|
||||
'3.26.0': '3.27.6',
|
||||
'3.25.0': '3.27.5',
|
||||
'3.24.6': '3.27.4',
|
||||
'3.24.5': '3.27.3',
|
||||
'3.24.4': '3.27.2',
|
||||
'3.24.3': '3.27.1',
|
||||
'3.24.3-alpha.1': '3.27.0',
|
||||
'3.24.3-alpha.0': '3.26.14',
|
||||
'3.24.2': '3.26.13',
|
||||
'3.24.2-alpha.1': '3.26.12',
|
||||
'3.24.2-alpha.0': '3.26.11',
|
||||
'3.24.1': '3.26.10',
|
||||
'3.24.1-alpha.0': '3.26.9',
|
||||
'3.24.0': '3.26.8',
|
||||
'3.24.0-alpha.7': '3.26.7',
|
||||
'3.24.0-alpha.6': '3.26.6',
|
||||
'3.24.0-alpha.5': '3.26.5',
|
||||
'3.24.0-alpha.4': '3.26.4',
|
||||
'3.24.0-alpha.3': '3.26.3',
|
||||
'3.24.0-alpha.2': '3.26.2',
|
||||
'3.24.0-alpha.1': '3.26.1',
|
||||
'3.24.0-alpha.0': '3.26.0',
|
||||
'3.23.0': '3.25.15',
|
||||
'3.23.0-alpha.1': '3.25.14',
|
||||
'3.23.0-alpha.0': '3.25.13',
|
||||
'3.22.7-alpha.0': '3.25.12',
|
||||
'3.22.6': '3.25.11',
|
||||
'3.22.6-alpha.0': '3.25.10',
|
||||
'3.22.5': '3.25.9',
|
||||
'3.22.4': '3.24.4',
|
||||
'3.22.3': '3.24.3',
|
||||
'3.22.3-alpha.9': '3.25.6',
|
||||
'3.22.3-alpha.8': '3.25.5',
|
||||
'3.22.3-alpha.7': '3.25.4',
|
||||
'3.22.3-alpha.6': '3.25.3',
|
||||
'3.22.3-alpha.5': '3.25.2',
|
||||
'3.22.3-alpha.4': '3.25.1',
|
||||
'3.22.3-alpha.3': '3.25.0',
|
||||
'3.22.3-alpha.2': '3.24.8',
|
||||
'3.22.3-alpha.1': '3.24.7',
|
||||
'3.22.3-alpha.0': '3.24.6',
|
||||
'3.22.2': '3.24.5',
|
||||
'3.22.1': '3.24.2',
|
||||
'3.22.0': '3.24.2-alpha.2',
|
||||
'3.21.0': '3.24.2-alpha.1',
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { KeyboardCommand } from './KeyboardCommands'
|
||||
|
||||
export type KeyboardCommandHandler = {
|
||||
command: KeyboardCommand
|
||||
onKeyDown?: (event: KeyboardEvent) => boolean | void
|
||||
onKeyUp?: (event: KeyboardEvent) => boolean | void
|
||||
onKeyDown?: (event: KeyboardEvent, data?: unknown) => boolean | void
|
||||
onKeyUp?: (event: KeyboardEvent, data?: unknown) => boolean | void
|
||||
element?: HTMLElement
|
||||
elements?: HTMLElement[]
|
||||
notElement?: HTMLElement
|
||||
|
||||
@@ -24,3 +24,7 @@ export const OPEN_NOTE_HISTORY_COMMAND = createKeyboardCommand('OPEN_NOTE_HISTOR
|
||||
export const CAPTURE_SAVE_COMMAND = createKeyboardCommand('CAPTURE_SAVE_COMMAND')
|
||||
export const STAR_NOTE_COMMAND = createKeyboardCommand('STAR_NOTE_COMMAND')
|
||||
export const PIN_NOTE_COMMAND = createKeyboardCommand('PIN_NOTE_COMMAND')
|
||||
export const SUPER_SHOW_MARKDOWN_PREVIEW = createKeyboardCommand('SUPER_SHOW_MARKDOWN_PREVIEW')
|
||||
export const SUPER_EXPORT_JSON = createKeyboardCommand('SUPER_EXPORT_JSON')
|
||||
export const SUPER_EXPORT_MARKDOWN = createKeyboardCommand('SUPER_EXPORT_MARKDOWN')
|
||||
export const SUPER_EXPORT_HTML = createKeyboardCommand('SUPER_EXPORT_HTML')
|
||||
|
||||
@@ -165,6 +165,22 @@ export class KeyboardService {
|
||||
}
|
||||
}
|
||||
|
||||
public triggerCommand(command: KeyboardCommand, data?: unknown): void {
|
||||
for (const observer of this.commandHandlers) {
|
||||
if (observer.command !== command) {
|
||||
continue
|
||||
}
|
||||
|
||||
const callback = observer.onKeyDown || observer.onKeyUp
|
||||
if (callback) {
|
||||
const exclusive = callback(new KeyboardEvent('command-trigger'), data)
|
||||
if (exclusive) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerShortcut(shortcut: KeyboardShortcut): void {
|
||||
this.commandMap.set(shortcut.command, shortcut)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user