mirror of
https://github.com/standardnotes/app
synced 2026-09-14 03:46:01 -04:00
Compare commits
98
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae03f84651 | ||
|
|
60e51d334c | ||
|
|
8c1f4bd064 | ||
|
|
2b845e814e | ||
|
|
313ce08c15 | ||
|
|
a811582a7f | ||
|
|
b40c65871a | ||
|
|
a7128fb6d6 | ||
|
|
a2dedd2675 | ||
|
|
071d5b1b56 | ||
|
|
0d3b7f7d94 | ||
|
|
1017683b44 | ||
|
|
74f8f9fafe | ||
|
|
c7d28d2b2f | ||
|
|
9fa6919618 | ||
|
|
9fbb845b1d | ||
|
|
488142683c | ||
|
|
8385b4e89f | ||
|
|
d9c480b67b | ||
|
|
c9d8a686ff | ||
|
|
85ad62389b | ||
|
|
6e6a511771 | ||
|
|
44e91b5ede | ||
|
|
4aad554c1a | ||
|
|
9f937f217b | ||
|
|
e3f8b81139 | ||
|
|
b117271d61 | ||
|
|
e635e06ca0 | ||
|
|
75ecf4c393 | ||
|
|
2eddc3a1c6 | ||
|
|
90d1ead9e3 | ||
|
|
7e3db49322 | ||
|
|
3df23cdb5c | ||
|
|
e739795396 | ||
|
|
b5f31c3ca3 | ||
|
|
053aaa5c9f | ||
|
|
9e0e176bac | ||
|
|
b1e211f91f | ||
|
|
880cceecd5 | ||
|
|
30c160f4b8 | ||
|
|
2b8d7e65ff | ||
|
|
6d326e2db4 | ||
|
|
9fe88c6c10 | ||
|
|
17dc74aaef | ||
|
|
9c8837ecdb | ||
|
|
e1a47ea884 | ||
|
|
06d6f3b481 | ||
|
|
0ab9006c52 | ||
|
|
4d9c9d597b | ||
|
|
40179bba06 | ||
|
|
8c548d40c4 | ||
|
|
a4cfd603d2 | ||
|
|
27ab5449f6 | ||
|
|
4740e43d1c | ||
|
|
b8f78eb4fd | ||
|
|
7c1ded30c7 | ||
|
|
247ff62422 | ||
|
|
420cb4d945 | ||
|
|
7b6e5f95d5 | ||
|
|
beddb3cc6c | ||
|
|
725b49568a | ||
|
|
6b89a6615f | ||
|
|
13d56082d8 | ||
|
|
91b3978e14 | ||
|
|
b34ab6ab28 | ||
|
|
07ec2be5ee | ||
|
|
d1009e368a | ||
|
|
b0cf179a90 | ||
|
|
2c4b574bd4 | ||
|
|
d658769531 | ||
|
|
a10d5a6728 | ||
|
|
67d3f149b2 | ||
|
|
0baf500616 | ||
|
|
ef27a21fb1 | ||
|
|
76bd995c9a | ||
|
|
3ada748522 | ||
|
|
f45a639a72 | ||
|
|
8bb5bb6f9f | ||
|
|
1925d8a9c2 | ||
|
|
93a1402088 | ||
|
|
cb15d183ca | ||
|
|
e686aa29e8 | ||
|
|
96905fe980 | ||
|
|
bd3fabcaa7 | ||
|
|
c6dcbf2cc9 | ||
|
|
a5d24fa526 | ||
|
|
aa16a287fa | ||
|
|
c41f686fd0 | ||
|
|
ec358d6b07 | ||
|
|
af1e18b47e | ||
|
|
681f717072 | ||
|
|
af84d09b7a | ||
|
|
ccac1494c2 | ||
|
|
51b2d8b115 | ||
|
|
d501ae4410 | ||
|
|
ac73dba1db | ||
|
|
157ad0b052 | ||
|
|
e79c4ceea4 |
@@ -0,0 +1,57 @@
|
||||
name: iOS Testflight
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
ios:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run:
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable && yarn install:pods
|
||||
- run: yarn build:mobile
|
||||
- name: Set ssh connection to Github
|
||||
uses: webfactory/[email protected]
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Export Apple AppStore Connect API key
|
||||
run: echo "${{ secrets.APPSTORE_CONNECT_KEY }}" > ios/Authkey.p8
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
working-directory: 'packages/mobile'
|
||||
- name: Setup fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
with:
|
||||
lane: 'ios setup'
|
||||
subdirectory: 'packages/mobile'
|
||||
- name: fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
APPLE_APP_ID: ${{ secrets.APP_APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ISSUER_ID }}
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
|
||||
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
MATCH_CERTIFICATES_URL: ${{ secrets.MATCH_CERTIFICATES_URL }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
with:
|
||||
lane: 'ios testflight'
|
||||
subdirectory: 'packages/mobile'
|
||||
@@ -66,12 +66,10 @@ jobs:
|
||||
packages/mobile/android/app/build/outputs/apk/prod/release/app-prod-release.apk
|
||||
|
||||
ios:
|
||||
env:
|
||||
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: macos-11
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.26.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.2",
|
||||
"version": "1.26.5",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.1.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
# [1.1.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **clipper:** Added "Clip as screenshot" switch to allow clipping content as a screenshot and save it to Files ([0d3b7f7](https://github.com/standardnotes/app/commit/0d3b7f7d94071675c1005bbe64fb11cc91bccf58))
|
||||
|
||||
## [1.0.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.0.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.0.22",
|
||||
"version": "1.1.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { runtime, action, browserAction, windows, storage } from 'webextension-polyfill'
|
||||
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||
import { runtime, action, browserAction, windows, storage, tabs } from 'webextension-polyfill'
|
||||
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||
|
||||
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
|
||||
|
||||
const openPopupAndClipSelection = async (payload: { title: string; content: string }) => {
|
||||
const openPopupAndClipSelection = async (payload: ClipPayload) => {
|
||||
await storage.local.set({ clip: payload })
|
||||
|
||||
if (isFirefox) {
|
||||
@@ -22,11 +22,15 @@ const openPopupAndClipSelection = async (payload: { title: string; content: stri
|
||||
void openPopup()
|
||||
}
|
||||
|
||||
runtime.onMessage.addListener((message: RuntimeMessage) => {
|
||||
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
|
||||
if (!message.payload) {
|
||||
return
|
||||
}
|
||||
void openPopupAndClipSelection(message.payload)
|
||||
} else if (message.type === RuntimeMessageTypes.CaptureVisibleTab) {
|
||||
return await tabs.captureVisibleTab(undefined, {
|
||||
format: 'png',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Readability } from '@mozilla/readability'
|
||||
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||
|
||||
let isSelectingNodeForClipping = false
|
||||
let isScreenshotMode = false
|
||||
|
||||
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
switch (message.type) {
|
||||
@@ -10,6 +11,10 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
isSelectingNodeForClipping = true
|
||||
return
|
||||
}
|
||||
case RuntimeMessageTypes.ToggleScreenshotMode: {
|
||||
isScreenshotMode = message.enabled
|
||||
return
|
||||
}
|
||||
case RuntimeMessageTypes.HasSelection: {
|
||||
const selection = window.getSelection()
|
||||
|
||||
@@ -40,6 +45,13 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
return { title: document.title, content: result.innerHTML, url: window.location.href }
|
||||
}
|
||||
case RuntimeMessageTypes.GetFullPage: {
|
||||
if (isScreenshotMode) {
|
||||
const content = await runtime.sendMessage({
|
||||
type: RuntimeMessageTypes.CaptureVisibleTab,
|
||||
})
|
||||
return { title: document.title, content: content, url: window.location.href, isScreenshot: true }
|
||||
}
|
||||
|
||||
return { title: document.title, content: document.body.innerHTML, url: window.location.href }
|
||||
}
|
||||
case RuntimeMessageTypes.GetArticle: {
|
||||
@@ -90,7 +102,56 @@ const disableNodeSelection = () => {
|
||||
nodeOverlayElement.style.visibility = 'hidden'
|
||||
}
|
||||
|
||||
window.addEventListener('click', (event) => {
|
||||
const imageFromBase64 = (base64: string) => {
|
||||
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const image = new Image()
|
||||
image.onload = () => {
|
||||
resolve(image)
|
||||
}
|
||||
image.onerror = reject
|
||||
image.src = base64
|
||||
})
|
||||
}
|
||||
|
||||
const toPng = async (element: HTMLElement) => {
|
||||
const visibleTab: string = await runtime.sendMessage({
|
||||
type: RuntimeMessageTypes.CaptureVisibleTab,
|
||||
})
|
||||
|
||||
const image = await imageFromBase64(visibleTab)
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Could not get canvas context')
|
||||
}
|
||||
|
||||
context.drawImage(image, 0, 0)
|
||||
|
||||
const elementRect = element.getBoundingClientRect()
|
||||
|
||||
const x = elementRect.x
|
||||
const y = elementRect.y
|
||||
|
||||
const width = elementRect.width
|
||||
const height = elementRect.height
|
||||
|
||||
const imageData = context.getImageData(x, y, width, height)
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
context.putImageData(imageData, 0, 0)
|
||||
|
||||
return canvas.toDataURL('image/png')
|
||||
}
|
||||
|
||||
window.addEventListener('click', async (event) => {
|
||||
if (!isSelectingNodeForClipping) {
|
||||
return
|
||||
}
|
||||
@@ -102,10 +163,10 @@ window.addEventListener('click', (event) => {
|
||||
return
|
||||
}
|
||||
const title = document.title
|
||||
const content = target.outerHTML
|
||||
const content = isScreenshotMode ? await toPng(target) : target.outerHTML
|
||||
void runtime.sendMessage({
|
||||
type: RuntimeMessageTypes.OpenPopupWithSelection,
|
||||
payload: { title, content, url: window.location.href },
|
||||
payload: { title, content, url: window.location.href, isScreenshot: isScreenshotMode },
|
||||
} as RuntimeMessage)
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Standard Notes Clipper",
|
||||
"description": "Web clipper for Standard Notes",
|
||||
"permissions": ["activeTab", "storage"],
|
||||
"permissions": ["activeTab", "storage", "<all_urls>"],
|
||||
"browser_action": {
|
||||
"default_popup": "popup/index.html?route=extension"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ export const RuntimeMessageTypes = {
|
||||
GetFullPage: 'get-full-page',
|
||||
OpenPopupWithSelection: 'open-popup-with-selection',
|
||||
StartNodeSelection: 'start-node-selection',
|
||||
ToggleScreenshotMode: 'toggle-screenshot-mode',
|
||||
CaptureVisibleTab: 'capture-visible-tab',
|
||||
} as const
|
||||
|
||||
export type RuntimeMessageType = typeof RuntimeMessageTypes[keyof typeof RuntimeMessageTypes]
|
||||
@@ -15,6 +17,7 @@ export type ClipPayload = {
|
||||
title: string
|
||||
content: string
|
||||
url: string
|
||||
isScreenshot?: boolean
|
||||
}
|
||||
|
||||
export type RuntimeMessageReturnTypes = {
|
||||
@@ -22,8 +25,10 @@ export type RuntimeMessageReturnTypes = {
|
||||
[RuntimeMessageTypes.GetSelection]: ClipPayload
|
||||
[RuntimeMessageTypes.HasSelection]: boolean
|
||||
[RuntimeMessageTypes.GetFullPage]: ClipPayload
|
||||
[RuntimeMessageTypes.CaptureVisibleTab]: string
|
||||
[RuntimeMessageTypes.OpenPopupWithSelection]: void
|
||||
[RuntimeMessageTypes.StartNodeSelection]: void
|
||||
[RuntimeMessageTypes.ToggleScreenshotMode]: void
|
||||
}
|
||||
|
||||
export type RuntimeMessage =
|
||||
@@ -32,5 +37,9 @@ export type RuntimeMessage =
|
||||
payload: ClipPayload
|
||||
}
|
||||
| {
|
||||
type: Exclude<RuntimeMessageType, MessagesWithClipPayload>
|
||||
type: typeof RuntimeMessageTypes.ToggleScreenshotMode
|
||||
enabled: boolean
|
||||
}
|
||||
| {
|
||||
type: Exclude<RuntimeMessageType, MessagesWithClipPayload | typeof RuntimeMessageTypes.ToggleScreenshotMode>
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { tabs } from 'webextension-polyfill'
|
||||
import { RuntimeMessageReturnTypes, RuntimeMessageType } from '../types/message'
|
||||
import { RuntimeMessage, RuntimeMessageReturnTypes } from '../types/message'
|
||||
|
||||
export default async function sendMessageToActiveTab<T extends RuntimeMessageType>(
|
||||
type: T,
|
||||
): Promise<RuntimeMessageReturnTypes[T] | undefined> {
|
||||
export default async function sendMessageToActiveTab<T extends RuntimeMessage>(
|
||||
message: T,
|
||||
): Promise<RuntimeMessageReturnTypes[T['type']] | undefined> {
|
||||
const [activeTab] = await tabs.query({ active: true, currentWindow: true, windowType: 'normal' })
|
||||
|
||||
if (!activeTab || !activeTab.id) {
|
||||
return
|
||||
}
|
||||
|
||||
return await tabs.sendMessage(activeTab.id, { type })
|
||||
return await tabs.sendMessage(activeTab.id, message)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PURCHASE_URL=https://website-dev.standardnotes.com/purchase
|
||||
PLANS_URL=https://website-dev.standardnotes.com/plans
|
||||
DASHBOARD_URL=https://website-dev.standardnotes.com/dashboard
|
||||
DEFAULT_SYNC_SERVER=https://api-dev.standardnotes.com
|
||||
PURCHASE_URL=https://standardnotes.com/purchase
|
||||
PLANS_URL=https://standardnotes.com/plans
|
||||
DASHBOARD_URL=https://standardnotes.com/dashboard
|
||||
DEFAULT_SYNC_SERVER=https://api.standardnotes.com
|
||||
@@ -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.106.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.106.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.106.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
|
||||
|
||||
## [3.105.86](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.85](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.84](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.83](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **desktop:** Fixed issue where preferences header & tooltips are overlapped by custom titlebar ([ef27a21](https://github.com/standardnotes/app/commit/ef27a21fb141c4ef1a760cf6fb550e4b713f8f98))
|
||||
|
||||
## [3.105.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.105.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,51 +1,17 @@
|
||||
# Standard Notes
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://twitter.com/standardnotes)
|
||||
|
||||
</div>
|
||||
|
||||
This application makes use of the core JS/CSS/HTML code found in the [web repo](https://github.com/standardnotes/app). For issues related to the actual app experience, please post issues in the web repo.
|
||||
# Standard Notes Desktop App
|
||||
|
||||
## Running Locally
|
||||
|
||||
Make sure [Yarn](https://classic.yarnpkg.com/en/) is installed on your system.
|
||||
Most commands below hog up a terminal process and must be conducted in different tabs. Be sure to quit any production version of the app running on your system first.
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn build:web # Or `yarn dev:web`
|
||||
yarn dev
|
||||
|
||||
# In another terminal
|
||||
yarn start
|
||||
cd packages/snjs && yarn start # optional to watch snjs changes
|
||||
cd packages/web && yarn watch # optional to watch web changes
|
||||
yarn dev # to start compilation watch process for electron-related code
|
||||
yarn start # to start dev app
|
||||
```
|
||||
|
||||
We use [commitlint](https://github.com/conventional-changelog/commitlint) to validate commit messages.
|
||||
Before making a pull request, make sure to check the output of the following commands:
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
yarn test # Make sure to start `yarn dev` before running the tests, and quit any running Standard Notes applications so they don't conflict.
|
||||
```
|
||||
|
||||
Pull requests should target the `develop` branch.
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
To determine where to install a dependency:
|
||||
|
||||
- If it is only required for building, install it in `package.json`'s `devDependencies`
|
||||
- If it is required at runtime but can be packaged by webpack, install it in `package.json`'s `dependencies`.
|
||||
- If it must be distributed as a node module (not packaged by webpack), install it in `app/package.json`'s `dependencies`
|
||||
- Also make sure to declare it as an external commonjs dependency in `webpack.common.js`.
|
||||
|
||||
## Building
|
||||
|
||||
Build for all platforms:
|
||||
|
||||
- `yarn release`
|
||||
|
||||
## Building natively on arm64
|
||||
|
||||
Building arm64 releases on amd64 systems is only possible with AppImage, Debian and universal "dir" targets.
|
||||
@@ -63,14 +29,6 @@ and making sure `$GEM_HOME/bin` is added to `$PATH`.
|
||||
|
||||
Snap releases also require a working snapcraft / `snapd` installation.
|
||||
|
||||
Building can then be done by running:
|
||||
|
||||
- `yarn install`
|
||||
|
||||
Followed by
|
||||
|
||||
- `node scripts/build.mjs deb-arm64`
|
||||
|
||||
## Installation
|
||||
|
||||
On Linux, download the latest AppImage from the [Releases](https://github.com/standardnotes/app/releases/latest) page, and give it executable permission:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { action, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../test/TestIpcMessage'
|
||||
import { Store } from './javascripts/Main/Store/Store'
|
||||
import { StoreKeys } from './javascripts/Main/Store/StoreKeys'
|
||||
@@ -14,7 +13,6 @@ export class AppState {
|
||||
readonly startUrl = Urls.indexHtml
|
||||
readonly isPrimaryInstance: boolean
|
||||
public willQuitApp = false
|
||||
public lastBackupDate: number | null = null
|
||||
public windowState?: WindowState
|
||||
public deepLinkUrl?: string
|
||||
public readonly updates: UpdateState
|
||||
@@ -28,11 +26,6 @@ export class AppState {
|
||||
this.lastRunVersion = this.store.get(StoreKeys.LastRunVersion) || 'unknown'
|
||||
this.store.set(StoreKeys.LastRunVersion, this.version)
|
||||
|
||||
makeObservable(this, {
|
||||
lastBackupDate: observable,
|
||||
setBackupCreationDate: action,
|
||||
})
|
||||
|
||||
this.updates = new UpdateState(this)
|
||||
|
||||
if (isTesting()) {
|
||||
@@ -45,8 +38,4 @@ export class AppState {
|
||||
public isRunningVersionForFirstTime(): boolean {
|
||||
return this.lastRunVersion !== this.version
|
||||
}
|
||||
|
||||
setBackupCreationDate(date: number | null): void {
|
||||
this.lastBackupDate = date
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { isDev } from './javascripts/Main/Utils/Utils'
|
||||
import { log as utilsLog } from '@standardnotes/utils'
|
||||
|
||||
export const isDevMode = isDev()
|
||||
|
||||
export enum LoggingDomain {
|
||||
Backups,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.Backups]: true,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDevMode || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
utilsLog(LoggingDomain[domain], ...args)
|
||||
}
|
||||
@@ -29,6 +29,10 @@ export function initializeApplication(args: { app: Electron.App; ipcMain: Electr
|
||||
if (isDev()) {
|
||||
/** Expose the app's state as a global variable. Useful for debugging */
|
||||
;(global as any).appState = state
|
||||
|
||||
setTimeout(() => {
|
||||
state.windowState?.window.webContents.openDevTools()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ function migrateSnapStorage() {
|
||||
error?.message ?? error,
|
||||
)
|
||||
}
|
||||
store.set(StoreKeys.BackupsLocation, newLocation)
|
||||
store.set(StoreKeys.LegacyTextBackupsLocation, newLocation)
|
||||
console.log('Migration: finished moving backups directory.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import { dialog, shell, WebContents } from 'electron'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { AppMessageType, MessageType } from '../../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../../AppState'
|
||||
import { MessageToWebApp } from '../../Shared/IpcMessages'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
import { backups as str } from '../Strings'
|
||||
import { Paths } from '../Types/Paths'
|
||||
import {
|
||||
deleteDir,
|
||||
deleteDirContents,
|
||||
ensureDirectoryExists,
|
||||
FileDoesNotExist,
|
||||
moveFiles,
|
||||
openDirectoryPicker,
|
||||
} from '../Utils/FileUtils'
|
||||
import { handleTestMessage, send } from '../Utils/Testing'
|
||||
import { isTesting, last } from '../Utils/Utils'
|
||||
import { BackupsManagerInterface } from './BackupsManagerInterface'
|
||||
|
||||
function log(...message: any) {
|
||||
console.log('BackupsManager:', ...message)
|
||||
}
|
||||
|
||||
function logError(...message: any) {
|
||||
console.error('BackupsManager:', ...message)
|
||||
}
|
||||
|
||||
export const enum EnsureRecentBackupExists {
|
||||
Success = 0,
|
||||
BackupsAreDisabled = 1,
|
||||
FailedToCreateBackup = 2,
|
||||
}
|
||||
|
||||
export const BackupsDirectoryName = 'Standard Notes Backups'
|
||||
const BackupFileExtension = '.txt'
|
||||
|
||||
function backupFileNameToDate(string: string): number {
|
||||
string = path.basename(string, '.txt')
|
||||
const dateTimeDelimiter = string.indexOf('T')
|
||||
const date = string.slice(0, dateTimeDelimiter)
|
||||
|
||||
const time = string.slice(dateTimeDelimiter + 1).replace(/-/g, ':')
|
||||
return Date.parse(date + 'T' + time)
|
||||
}
|
||||
|
||||
function dateToSafeFilename(date: Date) {
|
||||
return date.toISOString().replace(/:/g, '-')
|
||||
}
|
||||
|
||||
async function copyDecryptScript(location: string) {
|
||||
try {
|
||||
await ensureDirectoryExists(location)
|
||||
await fs.copyFile(Paths.decryptScript, path.join(location, path.basename(Paths.decryptScript)))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export function createBackupsManager(webContents: WebContents, appState: AppState): BackupsManagerInterface {
|
||||
let backupsLocation = appState.store.get(StoreKeys.BackupsLocation)
|
||||
let backupsDisabled = appState.store.get(StoreKeys.BackupsDisabled)
|
||||
let needsBackup = false
|
||||
|
||||
if (!backupsDisabled) {
|
||||
void copyDecryptScript(backupsLocation)
|
||||
}
|
||||
|
||||
determineLastBackupDate(backupsLocation)
|
||||
.then((date) => appState.setBackupCreationDate(date))
|
||||
.catch(console.error)
|
||||
|
||||
async function setBackupsLocation(location: string) {
|
||||
const previousLocation = backupsLocation
|
||||
if (previousLocation === location) {
|
||||
return
|
||||
}
|
||||
|
||||
const newLocation = path.join(location, BackupsDirectoryName)
|
||||
let previousLocationFiles = await fs.readdir(previousLocation)
|
||||
const backupFiles = previousLocationFiles
|
||||
.filter((fileName) => fileName.endsWith(BackupFileExtension))
|
||||
.map((fileName) => path.join(previousLocation, fileName))
|
||||
|
||||
await moveFiles(backupFiles, newLocation)
|
||||
await copyDecryptScript(newLocation)
|
||||
|
||||
previousLocationFiles = await fs.readdir(previousLocation)
|
||||
if (previousLocationFiles.length === 0 || previousLocationFiles[0] === path.basename(Paths.decryptScript)) {
|
||||
await deleteDir(previousLocation)
|
||||
}
|
||||
|
||||
/** Wait for the operation to be successful before saving new location */
|
||||
backupsLocation = newLocation
|
||||
appState.store.set(StoreKeys.BackupsLocation, backupsLocation)
|
||||
}
|
||||
|
||||
async function saveBackupData(data: any) {
|
||||
if (backupsDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
let success: boolean
|
||||
let name: string | undefined
|
||||
|
||||
try {
|
||||
name = await writeDataToFile(data)
|
||||
log(`Data backup successfully saved: ${name}`)
|
||||
success = true
|
||||
appState.setBackupCreationDate(Date.now())
|
||||
} catch (err) {
|
||||
success = false
|
||||
logError('An error occurred saving backup file', err)
|
||||
}
|
||||
|
||||
webContents.send(MessageToWebApp.FinishedSavingBackup, { success })
|
||||
|
||||
if (isTesting()) {
|
||||
send(AppMessageType.SavedBackup)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
function performBackup() {
|
||||
if (backupsDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
webContents.send(MessageToWebApp.PerformAutomatedBackup)
|
||||
}
|
||||
|
||||
async function writeDataToFile(data: any): Promise<string> {
|
||||
await ensureDirectoryExists(backupsLocation)
|
||||
|
||||
const name = dateToSafeFilename(new Date()) + BackupFileExtension
|
||||
const filePath = path.join(backupsLocation, name)
|
||||
await fs.writeFile(filePath, data)
|
||||
return name
|
||||
}
|
||||
|
||||
let interval: NodeJS.Timeout | undefined
|
||||
function beginBackups() {
|
||||
if (interval) {
|
||||
clearInterval(interval)
|
||||
}
|
||||
|
||||
needsBackup = true
|
||||
const hoursInterval = 12
|
||||
const seconds = hoursInterval * 60 * 60
|
||||
const milliseconds = seconds * 1000
|
||||
interval = setInterval(performBackup, milliseconds)
|
||||
}
|
||||
|
||||
function toggleBackupsStatus() {
|
||||
backupsDisabled = !backupsDisabled
|
||||
appState.store.set(StoreKeys.BackupsDisabled, backupsDisabled)
|
||||
/** Create a backup on reactivation. */
|
||||
if (!backupsDisabled) {
|
||||
performBackup()
|
||||
}
|
||||
}
|
||||
|
||||
if (isTesting()) {
|
||||
handleTestMessage(MessageType.DataArchive, (data: any) => saveBackupData(data))
|
||||
handleTestMessage(MessageType.BackupsAreEnabled, () => !backupsDisabled)
|
||||
handleTestMessage(MessageType.ToggleBackupsEnabled, toggleBackupsStatus)
|
||||
handleTestMessage(MessageType.BackupsLocation, () => backupsLocation)
|
||||
handleTestMessage(MessageType.PerformBackup, performBackup)
|
||||
handleTestMessage(MessageType.CopyDecryptScript, copyDecryptScript)
|
||||
handleTestMessage(MessageType.ChangeBackupsLocation, setBackupsLocation)
|
||||
}
|
||||
|
||||
return {
|
||||
get backupsAreEnabled() {
|
||||
return !backupsDisabled
|
||||
},
|
||||
get backupsLocation() {
|
||||
return backupsLocation
|
||||
},
|
||||
saveBackupData,
|
||||
performBackup,
|
||||
beginBackups,
|
||||
toggleBackupsStatus,
|
||||
async backupsCount(): Promise<number> {
|
||||
let files = await fs.readdir(backupsLocation)
|
||||
files = files.filter((fileName) => fileName.endsWith(BackupFileExtension))
|
||||
return files.length
|
||||
},
|
||||
applicationDidBlur() {
|
||||
if (needsBackup) {
|
||||
needsBackup = false
|
||||
performBackup()
|
||||
}
|
||||
},
|
||||
viewBackups() {
|
||||
void shell.openPath(backupsLocation)
|
||||
},
|
||||
async deleteBackups() {
|
||||
await deleteDirContents(backupsLocation)
|
||||
return copyDecryptScript(backupsLocation)
|
||||
},
|
||||
|
||||
async changeBackupsLocation() {
|
||||
const path = await openDirectoryPicker()
|
||||
|
||||
if (!path) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await setBackupsLocation(path)
|
||||
performBackup()
|
||||
} catch (e) {
|
||||
logError(e)
|
||||
void dialog.showMessageBox({
|
||||
message: str().errorChangingDirectory(e),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function determineLastBackupDate(backupsLocation: string): Promise<number | null> {
|
||||
try {
|
||||
const files = (await fs.readdir(backupsLocation))
|
||||
.filter((filename) => filename.endsWith(BackupFileExtension) && !Number.isNaN(backupFileNameToDate(filename)))
|
||||
.sort()
|
||||
const lastBackupFileName = last(files)
|
||||
if (!lastBackupFileName) {
|
||||
return null
|
||||
}
|
||||
const backupDate = backupFileNameToDate(lastBackupFileName)
|
||||
if (Number.isNaN(backupDate)) {
|
||||
return null
|
||||
}
|
||||
return backupDate
|
||||
} catch (error: any) {
|
||||
if (error.code !== FileDoesNotExist) {
|
||||
console.error(error)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
export interface BackupsManagerInterface {
|
||||
backupsAreEnabled: boolean
|
||||
toggleBackupsStatus(): void
|
||||
backupsLocation: string
|
||||
backupsCount(): Promise<number>
|
||||
applicationDidBlur(): void
|
||||
changeBackupsLocation(): void
|
||||
beginBackups(): void
|
||||
performBackup(): void
|
||||
deleteBackups(): Promise<void>
|
||||
viewBackups(): void
|
||||
saveBackupData(data: unknown): void
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
import { LoggingDomain, log } from './../../../Logging'
|
||||
import {
|
||||
FileBackupRecord,
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
PlaintextBackupsMapping,
|
||||
DesktopWatchedDirectoriesChange,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { AppState } from 'app/AppState'
|
||||
import { shell } from 'electron'
|
||||
import { promises as fs } from 'fs'
|
||||
import { WebContents, shell } from 'electron'
|
||||
import { StoreKeys } from '../Store/StoreKeys'
|
||||
import path from 'path'
|
||||
import {
|
||||
deleteFile,
|
||||
deleteFileIfExists,
|
||||
ensureDirectoryExists,
|
||||
moveDirContents,
|
||||
moveFile,
|
||||
openDirectoryPicker,
|
||||
readJSONFile,
|
||||
writeFile,
|
||||
@@ -20,6 +24,10 @@ import {
|
||||
} from '../Utils/FileUtils'
|
||||
import { FileDownloader } from './FileDownloader'
|
||||
import { FileReadOperation } from './FileReadOperation'
|
||||
import { Paths } from '../Types/Paths'
|
||||
import { MessageToWebApp } from '../../Shared/IpcMessages'
|
||||
|
||||
const TextBackupFileExtension = '.txt'
|
||||
|
||||
export const FileBackupsConstantsV1 = {
|
||||
Version: '1.0.0',
|
||||
@@ -29,107 +37,112 @@ export const FileBackupsConstantsV1 = {
|
||||
|
||||
export class FilesBackupManager implements FileBackupsDevice {
|
||||
private readOperations: Map<string, FileReadOperation> = new Map()
|
||||
private plaintextMappingCache?: PlaintextBackupsMapping
|
||||
|
||||
constructor(private appState: AppState) {}
|
||||
constructor(private appState: AppState, private webContents: WebContents) {}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
return Promise.resolve(this.appState.store.get(StoreKeys.FileBackupsEnabled))
|
||||
}
|
||||
private async findUuidForPlaintextBackupFileName(
|
||||
backupsDirectory: string,
|
||||
targetFilename: string,
|
||||
): Promise<string | undefined> {
|
||||
const mapping = await this.getPlaintextBackupsMappingFile(backupsDirectory)
|
||||
|
||||
public async enableFilesBackups(): Promise<void> {
|
||||
const currentLocation = await this.getFilesBackupsLocation()
|
||||
|
||||
if (!currentLocation) {
|
||||
const result = await this.changeFilesBackupsLocation()
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
const uuid = Object.keys(mapping.files).find((uuid) => {
|
||||
const entries = mapping.files[uuid]
|
||||
for (const entry of entries) {
|
||||
const filePath = entry.path
|
||||
const filename = path.basename(filePath)
|
||||
if (filename === targetFilename) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
this.appState.store.set(StoreKeys.FileBackupsEnabled, true)
|
||||
|
||||
const mapping = this.getMappingFileFromDisk()
|
||||
|
||||
if (!mapping) {
|
||||
await this.saveFilesBackupsMappingFile(this.defaultMappingFileValue())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
public disableFilesBackups(): Promise<void> {
|
||||
this.appState.store.set(StoreKeys.FileBackupsEnabled, false)
|
||||
public async migrateLegacyFileBackupsToNewStructure(newLocation: string): Promise<void> {
|
||||
const legacyLocation = await this.getLegacyFilesBackupsLocation()
|
||||
if (!legacyLocation) {
|
||||
return
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
await ensureDirectoryExists(newLocation)
|
||||
|
||||
const legacyMappingLocation = `${legacyLocation}/info.json`
|
||||
const newMappingLocation = this.getFileBackupsMappingFilePath(newLocation)
|
||||
await ensureDirectoryExists(path.dirname(newMappingLocation))
|
||||
await moveFile(legacyMappingLocation, newMappingLocation)
|
||||
|
||||
await moveDirContents(legacyLocation, newLocation)
|
||||
}
|
||||
|
||||
public async changeFilesBackupsLocation(): Promise<string | undefined> {
|
||||
const newPath = await openDirectoryPicker()
|
||||
public async isLegacyFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.appState.store.get(StoreKeys.LegacyFileBackupsEnabled)
|
||||
}
|
||||
|
||||
if (!newPath) {
|
||||
async wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
|
||||
const value = this.appState.store.get(StoreKeys.LegacyTextBackupsDisabled)
|
||||
return value === true
|
||||
}
|
||||
|
||||
async getUserDocumentsDirectory(): Promise<string> {
|
||||
return Paths.documentsDir
|
||||
}
|
||||
|
||||
public async getLegacyFilesBackupsLocation(): Promise<string | undefined> {
|
||||
return this.appState.store.get(StoreKeys.LegacyFileBackupsLocation)
|
||||
}
|
||||
|
||||
async getLegacyTextBackupsLocation(): Promise<string | undefined> {
|
||||
const savedLocation = this.appState.store.get(StoreKeys.LegacyTextBackupsLocation)
|
||||
if (savedLocation) {
|
||||
return savedLocation
|
||||
}
|
||||
|
||||
const LegacyTextBackupsDirectory = 'Standard Notes Backups'
|
||||
return `${Paths.homeDir}/${LegacyTextBackupsDirectory}`
|
||||
}
|
||||
|
||||
public async presentDirectoryPickerForLocationChangeAndTransferOld(
|
||||
appendPath: string,
|
||||
oldLocation?: string,
|
||||
): Promise<string | undefined> {
|
||||
const selectedDirectory = await openDirectoryPicker('Select')
|
||||
|
||||
if (!selectedDirectory) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const oldPath = await this.getFilesBackupsLocation()
|
||||
const newPath = path.join(selectedDirectory, path.normalize(appendPath))
|
||||
|
||||
if (oldPath) {
|
||||
await this.transferFilesBackupsToNewLocation(oldPath, newPath)
|
||||
} else {
|
||||
this.appState.store.set(StoreKeys.FileBackupsLocation, newPath)
|
||||
await ensureDirectoryExists(newPath)
|
||||
|
||||
if (oldLocation) {
|
||||
await moveDirContents(path.normalize(oldLocation), newPath)
|
||||
}
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
private async transferFilesBackupsToNewLocation(oldPath: string, newPath: string): Promise<void> {
|
||||
const mapping = await this.getMappingFileFromDisk()
|
||||
if (!mapping) {
|
||||
return
|
||||
}
|
||||
|
||||
const entries = Object.values(mapping.files)
|
||||
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)
|
||||
}
|
||||
|
||||
const oldMappingFileLocation = this.getMappingFileLocation()
|
||||
|
||||
this.appState.store.set(StoreKeys.FileBackupsLocation, newPath)
|
||||
|
||||
const result = await this.saveFilesBackupsMappingFile(mapping)
|
||||
|
||||
if (result === 'success') {
|
||||
await deleteFile(oldMappingFileLocation)
|
||||
}
|
||||
private getFileBackupsMappingFilePath(backupsLocation: string): string {
|
||||
return `${backupsLocation}/.settings/info.json`
|
||||
}
|
||||
|
||||
public getFilesBackupsLocation(): Promise<string> {
|
||||
return Promise.resolve(this.appState.store.get(StoreKeys.FileBackupsLocation))
|
||||
private async getFileBackupsMappingFileFromDisk(backupsLocation: string): Promise<FileBackupsMapping | undefined> {
|
||||
return readJSONFile<FileBackupsMapping>(this.getFileBackupsMappingFilePath(backupsLocation))
|
||||
}
|
||||
|
||||
private getMappingFileLocation(): string {
|
||||
const base = this.appState.store.get(StoreKeys.FileBackupsLocation)
|
||||
return `${base}/info.json`
|
||||
}
|
||||
|
||||
private async getMappingFileFromDisk(): Promise<FileBackupsMapping | undefined> {
|
||||
return readJSONFile<FileBackupsMapping>(this.getMappingFileLocation())
|
||||
}
|
||||
|
||||
private defaultMappingFileValue(): FileBackupsMapping {
|
||||
private defaulFileBackupstMappingFileValue(): FileBackupsMapping {
|
||||
return { version: FileBackupsConstantsV1.Version, files: {} }
|
||||
}
|
||||
|
||||
async getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
|
||||
const data = await this.getMappingFileFromDisk()
|
||||
async getFilesBackupsMappingFile(backupsLocation: string): Promise<FileBackupsMapping> {
|
||||
const data = await this.getFileBackupsMappingFileFromDisk(backupsLocation)
|
||||
|
||||
if (!data) {
|
||||
return this.defaultMappingFileValue()
|
||||
return this.defaulFileBackupstMappingFileValue()
|
||||
}
|
||||
|
||||
for (const entry of Object.values(data.files)) {
|
||||
@@ -139,23 +152,18 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
return data
|
||||
}
|
||||
|
||||
async openFilesBackupsLocation(): Promise<void> {
|
||||
const location = await this.getFilesBackupsLocation()
|
||||
|
||||
async openLocation(location: string): Promise<void> {
|
||||
void shell.openPath(location)
|
||||
}
|
||||
|
||||
async openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
void shell.openPath(record.absolutePath)
|
||||
}
|
||||
|
||||
async saveFilesBackupsMappingFile(file: FileBackupsMapping): Promise<'success' | 'failed'> {
|
||||
await writeJSONFile(this.getMappingFileLocation(), file)
|
||||
private async saveFilesBackupsMappingFile(location: string, file: FileBackupsMapping): Promise<'success' | 'failed'> {
|
||||
await writeJSONFile(this.getFileBackupsMappingFilePath(location), file)
|
||||
|
||||
return 'success'
|
||||
}
|
||||
|
||||
async saveFilesBackupsFile(
|
||||
location: string,
|
||||
uuid: string,
|
||||
metaFile: string,
|
||||
downloadRequest: {
|
||||
@@ -164,9 +172,7 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'> {
|
||||
const backupsDir = await this.getFilesBackupsLocation()
|
||||
|
||||
const fileDir = `${backupsDir}/${uuid}`
|
||||
const fileDir = `${location}/${uuid}`
|
||||
const metaFilePath = `${fileDir}/${FileBackupsConstantsV1.MetadataFileName}`
|
||||
const binaryPath = `${fileDir}/${FileBackupsConstantsV1.BinaryFileName}`
|
||||
|
||||
@@ -184,25 +190,24 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
const result = await downloader.run()
|
||||
|
||||
if (result === 'success') {
|
||||
const mapping = await this.getFilesBackupsMappingFile()
|
||||
const mapping = await this.getFilesBackupsMappingFile(location)
|
||||
|
||||
mapping.files[uuid] = {
|
||||
backedUpOn: new Date(),
|
||||
absolutePath: fileDir,
|
||||
relativePath: uuid,
|
||||
metadataFileName: FileBackupsConstantsV1.MetadataFileName,
|
||||
binaryFileName: FileBackupsConstantsV1.BinaryFileName,
|
||||
version: FileBackupsConstantsV1.Version,
|
||||
}
|
||||
|
||||
await this.saveFilesBackupsMappingFile(mapping)
|
||||
await this.saveFilesBackupsMappingFile(location, mapping)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
const operation = new FileReadOperation(record)
|
||||
async getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
|
||||
const operation = new FileReadOperation(filePath)
|
||||
|
||||
this.readOperations.set(operation.token, operation)
|
||||
|
||||
@@ -224,4 +229,180 @@ export class FilesBackupManager implements FileBackupsDevice {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async getTextBackupsCount(location: string): Promise<number> {
|
||||
let files = await fs.readdir(location)
|
||||
files = files.filter((fileName) => fileName.endsWith(TextBackupFileExtension))
|
||||
return files.length
|
||||
}
|
||||
|
||||
async saveTextBackupData(location: string, data: string): Promise<void> {
|
||||
log(LoggingDomain.Backups, 'Saving text backup data', 'to', location)
|
||||
let success: boolean
|
||||
|
||||
try {
|
||||
await ensureDirectoryExists(location)
|
||||
const name = `${new Date().toISOString().replace(/:/g, '-')}${TextBackupFileExtension}`
|
||||
const filePath = path.join(location, name)
|
||||
await fs.writeFile(filePath, data)
|
||||
success = true
|
||||
} catch (err) {
|
||||
success = false
|
||||
console.error('An error occurred saving backup file', err)
|
||||
}
|
||||
|
||||
log(LoggingDomain.Backups, 'Finished saving text backup data', { success })
|
||||
}
|
||||
|
||||
async copyDecryptScript(location: string) {
|
||||
try {
|
||||
await ensureDirectoryExists(location)
|
||||
await fs.copyFile(Paths.decryptScript, path.join(location, path.basename(Paths.decryptScript)))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private getPlaintextMappingFilePath(location: string): string {
|
||||
return `${location}/.settings/info.json`
|
||||
}
|
||||
|
||||
private async getPlaintextMappingFileFromDisk(location: string): Promise<PlaintextBackupsMapping | undefined> {
|
||||
return readJSONFile<PlaintextBackupsMapping>(this.getPlaintextMappingFilePath(location))
|
||||
}
|
||||
|
||||
private async savePlaintextBackupsMappingFile(
|
||||
location: string,
|
||||
file: PlaintextBackupsMapping,
|
||||
): Promise<'success' | 'failed'> {
|
||||
await writeJSONFile(this.getPlaintextMappingFilePath(location), file)
|
||||
|
||||
return 'success'
|
||||
}
|
||||
|
||||
private defaultPlaintextMappingFileValue(): PlaintextBackupsMapping {
|
||||
return { version: '1.0', files: {} }
|
||||
}
|
||||
|
||||
async getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
|
||||
if (this.plaintextMappingCache) {
|
||||
return this.plaintextMappingCache
|
||||
}
|
||||
|
||||
let data = await this.getPlaintextMappingFileFromDisk(location)
|
||||
|
||||
if (!data) {
|
||||
data = this.defaultPlaintextMappingFileValue()
|
||||
}
|
||||
|
||||
this.plaintextMappingCache = data
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async savePlaintextNoteBackup(
|
||||
location: string,
|
||||
uuid: string,
|
||||
name: string,
|
||||
tags: string[],
|
||||
data: string,
|
||||
): Promise<void> {
|
||||
log(LoggingDomain.Backups, 'Saving plaintext note backup', uuid, 'to', location)
|
||||
|
||||
const mapping = await this.getPlaintextBackupsMappingFile(location)
|
||||
if (!mapping.files[uuid]) {
|
||||
mapping.files[uuid] = []
|
||||
}
|
||||
|
||||
const removeNoteFromAllDirectories = async () => {
|
||||
const records = mapping.files[uuid]
|
||||
for (const record of records) {
|
||||
const filePath = path.join(location, record.path)
|
||||
await deleteFileIfExists(filePath)
|
||||
}
|
||||
mapping.files[uuid] = []
|
||||
}
|
||||
|
||||
await removeNoteFromAllDirectories()
|
||||
|
||||
const writeFileToPath = async (absolutePath: string, filename: string, data: string, forTag?: string) => {
|
||||
const findMappingRecord = (tag?: string) => {
|
||||
const records = mapping.files[uuid]
|
||||
return records.find((record) => record.tag === tag)
|
||||
}
|
||||
|
||||
await ensureDirectoryExists(absolutePath)
|
||||
|
||||
const relativePath = forTag ?? ''
|
||||
const filenameWithSlashesEscaped = filename.replace(/\//g, '\u2215')
|
||||
const fileAbsolutePath = path.join(absolutePath, relativePath, filenameWithSlashesEscaped)
|
||||
await writeFile(fileAbsolutePath, data)
|
||||
|
||||
const existingRecord = findMappingRecord(forTag)
|
||||
if (!existingRecord) {
|
||||
mapping.files[uuid].push({
|
||||
tag: forTag,
|
||||
path: path.join(relativePath, filename),
|
||||
})
|
||||
} else {
|
||||
existingRecord.path = path.join(relativePath, filename)
|
||||
existingRecord.tag = forTag
|
||||
}
|
||||
}
|
||||
|
||||
const uuidPart = uuid.split('-')[0]
|
||||
const condensedUuidPart = uuidPart.substring(0, 4)
|
||||
if (tags.length === 0) {
|
||||
await writeFileToPath(location, `${name}-${condensedUuidPart}.txt`, data)
|
||||
} else {
|
||||
for (const tag of tags) {
|
||||
await writeFileToPath(location, `${name}-${condensedUuidPart}.txt`, data, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async persistPlaintextBackupsMappingFile(location: string): Promise<void> {
|
||||
if (!this.plaintextMappingCache) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.savePlaintextBackupsMappingFile(location, this.plaintextMappingCache)
|
||||
}
|
||||
|
||||
async monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
|
||||
const FEATURE_ENABLED = false
|
||||
if (!FEATURE_ENABLED) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const watcher = fs.watch(backupsDirectory, { recursive: true })
|
||||
for await (const event of watcher) {
|
||||
const { eventType, filename } = event
|
||||
if (eventType !== 'change' && eventType !== 'rename') {
|
||||
continue
|
||||
}
|
||||
const itemUuid = await this.findUuidForPlaintextBackupFileName(backupsDirectory, filename)
|
||||
if (itemUuid) {
|
||||
try {
|
||||
const change: DesktopWatchedDirectoriesChange = {
|
||||
itemUuid,
|
||||
path: path.join(backupsDirectory, filename),
|
||||
type: eventType,
|
||||
content: await fs.readFile(path.join(backupsDirectory, filename), 'utf-8'),
|
||||
}
|
||||
this.webContents.send(MessageToWebApp.WatchedDirectoriesChanges, [change])
|
||||
} catch (err) {
|
||||
log(LoggingDomain.Backups, 'Error processing watched change', err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if ((err as Error).name === 'AbortError') {
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FileBackupReadChunkResponse, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { FileBackupReadChunkResponse } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const ONE_MB = 1024 * 1024
|
||||
const CHUNK_LIMIT = ONE_MB * 5
|
||||
@@ -11,9 +10,9 @@ export class FileReadOperation {
|
||||
private localFileId: number
|
||||
private fileLength: number
|
||||
|
||||
constructor(backupRecord: FileBackupRecord) {
|
||||
this.token = backupRecord.absolutePath
|
||||
this.localFileId = fs.openSync(path.join(backupRecord.absolutePath, backupRecord.binaryFileName), 'r')
|
||||
constructor(filePath: string) {
|
||||
this.token = filePath
|
||||
this.localFileId = fs.openSync(filePath, 'r')
|
||||
this.fileLength = fs.fstatSync(this.localFileId).size
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { checkForUpdate, openChangelog, showUpdateInstallationDialog } from '../
|
||||
import { handleTestMessage } from '../Utils/Testing'
|
||||
import { isDev, isTesting } from '../Utils/Utils'
|
||||
import { MessageType } from './../../../../test/TestIpcMessage'
|
||||
import { BackupsManagerInterface } from './../Backups/BackupsManagerInterface'
|
||||
import { SpellcheckerManager } from './../SpellcheckerManager'
|
||||
import { MenuManagerInterface } from './MenuManagerInterface'
|
||||
|
||||
@@ -112,14 +111,12 @@ function suggestionsMenu(
|
||||
export function createMenuManager({
|
||||
window,
|
||||
appState,
|
||||
backupsManager,
|
||||
trayManager,
|
||||
store,
|
||||
spellcheckerManager,
|
||||
}: {
|
||||
window: Electron.BrowserWindow
|
||||
appState: AppState
|
||||
backupsManager: BackupsManagerInterface
|
||||
trayManager: TrayManager
|
||||
store: Store
|
||||
spellcheckerManager?: SpellcheckerManager
|
||||
@@ -167,7 +164,6 @@ export function createMenuManager({
|
||||
editMenu(spellcheckerManager, reload),
|
||||
viewMenu(window, store, reload),
|
||||
windowMenu(store, trayManager, reload),
|
||||
backupsMenu(backupsManager, reload),
|
||||
updateMenu(window, appState),
|
||||
...(isLinux() ? [keyringMenu(window, store)] : []),
|
||||
helpMenu(window, shell),
|
||||
@@ -468,34 +464,6 @@ function minimizeToTrayItem(store: Store, trayManager: TrayManager, reload: () =
|
||||
}
|
||||
}
|
||||
|
||||
function backupsMenu(archiveManager: BackupsManagerInterface, reload: () => any) {
|
||||
return {
|
||||
label: str().backups,
|
||||
submenu: [
|
||||
{
|
||||
label: archiveManager.backupsAreEnabled ? str().disableAutomaticBackups : str().enableAutomaticBackups,
|
||||
click() {
|
||||
archiveManager.toggleBackupsStatus()
|
||||
reload()
|
||||
},
|
||||
},
|
||||
Separator,
|
||||
{
|
||||
label: str().changeBackupsLocation,
|
||||
click() {
|
||||
archiveManager.changeBackupsLocation()
|
||||
},
|
||||
},
|
||||
{
|
||||
label: str().openBackupsLocation,
|
||||
click() {
|
||||
void shell.openPath(archiveManager.backupsLocation)
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function updateMenu(window: BrowserWindow, appState: AppState) {
|
||||
const updateState = appState.updates
|
||||
let label
|
||||
|
||||
@@ -8,12 +8,11 @@ const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
import {
|
||||
FileBackupsDevice,
|
||||
FileBackupsMapping,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
PlaintextBackupsMapping,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import { BackupsManagerInterface } from '../Backups/BackupsManagerInterface'
|
||||
import { KeychainInterface } from '../Keychain/KeychainInterface'
|
||||
import { MenuManagerInterface } from '../Menus/MenuManagerInterface'
|
||||
import { Component, PackageManagerInterface } from '../Packages/PackageManagerInterface'
|
||||
@@ -29,7 +28,6 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
constructor(
|
||||
private window: BrowserWindow,
|
||||
private keychain: KeychainInterface,
|
||||
private backups: BackupsManagerInterface,
|
||||
private packages: PackageManagerInterface,
|
||||
private search: SearchManagerInterface,
|
||||
private data: RemoteDataInterface,
|
||||
@@ -54,28 +52,30 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
getKeychainValue: this.getKeychainValue.bind(this),
|
||||
setKeychainValue: this.setKeychainValue.bind(this),
|
||||
clearKeychainValue: this.clearKeychainValue.bind(this),
|
||||
localBackupsCount: this.localBackupsCount.bind(this),
|
||||
viewlocalBackups: this.viewlocalBackups.bind(this),
|
||||
deleteLocalBackups: this.deleteLocalBackups.bind(this),
|
||||
displayAppMenu: this.displayAppMenu.bind(this),
|
||||
saveDataBackup: this.saveDataBackup.bind(this),
|
||||
syncComponents: this.syncComponents.bind(this),
|
||||
onMajorDataChange: this.onMajorDataChange.bind(this),
|
||||
onSearch: this.onSearch.bind(this),
|
||||
onInitialDataLoad: this.onInitialDataLoad.bind(this),
|
||||
destroyAllData: this.destroyAllData.bind(this),
|
||||
getFilesBackupsMappingFile: this.getFilesBackupsMappingFile.bind(this),
|
||||
saveFilesBackupsFile: this.saveFilesBackupsFile.bind(this),
|
||||
isFilesBackupsEnabled: this.isFilesBackupsEnabled.bind(this),
|
||||
enableFilesBackups: this.enableFilesBackups.bind(this),
|
||||
disableFilesBackups: this.disableFilesBackups.bind(this),
|
||||
changeFilesBackupsLocation: this.changeFilesBackupsLocation.bind(this),
|
||||
getFilesBackupsLocation: this.getFilesBackupsLocation.bind(this),
|
||||
openFilesBackupsLocation: this.openFilesBackupsLocation.bind(this),
|
||||
openFileBackup: this.openFileBackup.bind(this),
|
||||
isLegacyFilesBackupsEnabled: this.isLegacyFilesBackupsEnabled.bind(this),
|
||||
getLegacyFilesBackupsLocation: this.getLegacyFilesBackupsLocation.bind(this),
|
||||
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
|
||||
readNextChunk: this.readNextChunk.bind(this),
|
||||
askForMediaAccess: this.askForMediaAccess.bind(this),
|
||||
wasLegacyTextBackupsExplicitlyDisabled: this.wasLegacyTextBackupsExplicitlyDisabled.bind(this),
|
||||
getLegacyTextBackupsLocation: this.getLegacyTextBackupsLocation.bind(this),
|
||||
saveTextBackupData: this.saveTextBackupData.bind(this),
|
||||
savePlaintextNoteBackup: this.savePlaintextNoteBackup.bind(this),
|
||||
openLocation: this.openLocation.bind(this),
|
||||
presentDirectoryPickerForLocationChangeAndTransferOld:
|
||||
this.presentDirectoryPickerForLocationChangeAndTransferOld.bind(this),
|
||||
getPlaintextBackupsMappingFile: this.getPlaintextBackupsMappingFile.bind(this),
|
||||
persistPlaintextBackupsMappingFile: this.persistPlaintextBackupsMappingFile.bind(this),
|
||||
getTextBackupsCount: this.getTextBackupsCount.bind(this),
|
||||
migrateLegacyFileBackupsToNewStructure: this.migrateLegacyFileBackupsToNewStructure.bind(this),
|
||||
getUserDocumentsDirectory: this.getUserDocumentsDirectory.bind(this),
|
||||
monitorPlaintextBackupsLocationForChanges: this.monitorPlaintextBackupsLocationForChanges.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,51 +135,28 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
return this.keychain.clearKeychainValue()
|
||||
}
|
||||
|
||||
async localBackupsCount() {
|
||||
return this.backups.backupsCount()
|
||||
}
|
||||
|
||||
viewlocalBackups() {
|
||||
this.backups.viewBackups()
|
||||
}
|
||||
|
||||
async deleteLocalBackups() {
|
||||
return this.backups.deleteBackups()
|
||||
}
|
||||
|
||||
syncComponents(components: Component[]) {
|
||||
void this.packages.syncComponents(components)
|
||||
}
|
||||
|
||||
onMajorDataChange() {
|
||||
this.backups.performBackup()
|
||||
}
|
||||
|
||||
onSearch(text: string) {
|
||||
this.search.findInPage(text)
|
||||
}
|
||||
|
||||
onInitialDataLoad() {
|
||||
this.backups.beginBackups()
|
||||
}
|
||||
|
||||
destroyAllData() {
|
||||
this.data.destroySensitiveDirectories()
|
||||
}
|
||||
|
||||
saveDataBackup(data: unknown) {
|
||||
this.backups.saveBackupData(data)
|
||||
}
|
||||
|
||||
displayAppMenu() {
|
||||
this.menus.popupMenu()
|
||||
}
|
||||
|
||||
getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
|
||||
return this.fileBackups.getFilesBackupsMappingFile()
|
||||
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
|
||||
return this.fileBackups.getFilesBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
saveFilesBackupsFile(
|
||||
location: string,
|
||||
uuid: string,
|
||||
metaFile: string,
|
||||
downloadRequest: {
|
||||
@@ -188,43 +165,74 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'> {
|
||||
return this.fileBackups.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
return this.fileBackups.saveFilesBackupsFile(location, uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.fileBackups.getFileBackupReadToken(record)
|
||||
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
|
||||
return this.fileBackups.getFileBackupReadToken(filePath)
|
||||
}
|
||||
|
||||
readNextChunk(nextToken: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.fileBackups.readNextChunk(nextToken)
|
||||
}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.fileBackups.isFilesBackupsEnabled()
|
||||
public isLegacyFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.fileBackups.isLegacyFilesBackupsEnabled()
|
||||
}
|
||||
|
||||
public enableFilesBackups(): Promise<void> {
|
||||
return this.fileBackups.enableFilesBackups()
|
||||
public getLegacyFilesBackupsLocation(): Promise<string | undefined> {
|
||||
return this.fileBackups.getLegacyFilesBackupsLocation()
|
||||
}
|
||||
|
||||
public disableFilesBackups(): Promise<void> {
|
||||
return this.fileBackups.disableFilesBackups()
|
||||
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
|
||||
return this.fileBackups.wasLegacyTextBackupsExplicitlyDisabled()
|
||||
}
|
||||
|
||||
public changeFilesBackupsLocation(): Promise<string | undefined> {
|
||||
return this.fileBackups.changeFilesBackupsLocation()
|
||||
getLegacyTextBackupsLocation(): Promise<string | undefined> {
|
||||
return this.fileBackups.getLegacyTextBackupsLocation()
|
||||
}
|
||||
|
||||
public getFilesBackupsLocation(): Promise<string> {
|
||||
return this.fileBackups.getFilesBackupsLocation()
|
||||
saveTextBackupData(location: string, data: string): Promise<void> {
|
||||
return this.fileBackups.saveTextBackupData(location, data)
|
||||
}
|
||||
|
||||
public openFilesBackupsLocation(): Promise<void> {
|
||||
return this.fileBackups.openFilesBackupsLocation()
|
||||
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void> {
|
||||
return this.fileBackups.savePlaintextNoteBackup(location, uuid, name, tags, data)
|
||||
}
|
||||
|
||||
public openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
return this.fileBackups.openFileBackup(record)
|
||||
openLocation(path: string): Promise<void> {
|
||||
return this.fileBackups.openLocation(path)
|
||||
}
|
||||
|
||||
presentDirectoryPickerForLocationChangeAndTransferOld(
|
||||
appendPath: string,
|
||||
oldLocation?: string | undefined,
|
||||
): Promise<string | undefined> {
|
||||
return this.fileBackups.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
|
||||
}
|
||||
|
||||
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
|
||||
return this.fileBackups.getPlaintextBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
persistPlaintextBackupsMappingFile(location: string): Promise<void> {
|
||||
return this.fileBackups.persistPlaintextBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
getTextBackupsCount(location: string): Promise<number> {
|
||||
return this.fileBackups.getTextBackupsCount(location)
|
||||
}
|
||||
|
||||
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void> {
|
||||
return this.fileBackups.migrateLegacyFileBackupsToNewStructure(newPath)
|
||||
}
|
||||
|
||||
getUserDocumentsDirectory(): Promise<string> {
|
||||
return this.fileBackups.getUserDocumentsDirectory()
|
||||
}
|
||||
|
||||
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
|
||||
return this.fileBackups.monitorPlaintextBackupsLocationForChanges(backupsDirectory)
|
||||
}
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
|
||||
@@ -156,7 +156,7 @@ export function createSpellcheckerManager(
|
||||
const firstOutlier = session.availableSpellCheckerLanguages.find(
|
||||
(language, index) => availableSpellCheckerLanguages[index] !== language,
|
||||
)
|
||||
throw new Error(`Found unsupported language code: ${firstOutlier}`)
|
||||
console.error(`Found unsupported language code: ${firstOutlier}`)
|
||||
}
|
||||
|
||||
setSpellcheckerLanguages()
|
||||
|
||||
@@ -4,30 +4,34 @@ export enum StoreKeys {
|
||||
ExtServerHost = 'extServerHost',
|
||||
UseSystemMenuBar = 'useSystemMenuBar',
|
||||
MenuBarVisible = 'isMenuBarVisible',
|
||||
BackupsLocation = 'backupsLocation',
|
||||
BackupsDisabled = 'backupsDisabled',
|
||||
MinimizeToTray = 'minimizeToTray',
|
||||
EnableAutoUpdate = 'enableAutoUpdates',
|
||||
ZoomFactor = 'zoomFactor',
|
||||
SelectedSpellCheckerLanguageCodes = 'selectedSpellCheckerLanguageCodes',
|
||||
UseNativeKeychain = 'useNativeKeychain',
|
||||
FileBackupsEnabled = 'fileBackupsEnabled',
|
||||
FileBackupsLocation = 'fileBackupsLocation',
|
||||
LastRunVersion = 'LastRunVersion',
|
||||
|
||||
LegacyTextBackupsLocation = 'backupsLocation',
|
||||
LegacyTextBackupsDisabled = 'backupsDisabled',
|
||||
|
||||
LegacyFileBackupsEnabled = 'fileBackupsEnabled',
|
||||
LegacyFileBackupsLocation = 'fileBackupsLocation',
|
||||
}
|
||||
|
||||
export interface StoreData {
|
||||
[StoreKeys.ExtServerHost]: string
|
||||
[StoreKeys.UseSystemMenuBar]: boolean
|
||||
[StoreKeys.MenuBarVisible]: boolean
|
||||
[StoreKeys.BackupsLocation]: string
|
||||
[StoreKeys.BackupsDisabled]: boolean
|
||||
[StoreKeys.MinimizeToTray]: boolean
|
||||
[StoreKeys.EnableAutoUpdate]: boolean
|
||||
[StoreKeys.UseNativeKeychain]: boolean | null
|
||||
[StoreKeys.ZoomFactor]: number
|
||||
[StoreKeys.SelectedSpellCheckerLanguageCodes]: Set<Language> | null
|
||||
[StoreKeys.FileBackupsEnabled]: boolean
|
||||
[StoreKeys.FileBackupsLocation]: string
|
||||
[StoreKeys.LastRunVersion]: string
|
||||
|
||||
[StoreKeys.LegacyTextBackupsLocation]: string
|
||||
[StoreKeys.LegacyTextBackupsDisabled]: boolean
|
||||
|
||||
[StoreKeys.LegacyFileBackupsEnabled]: boolean
|
||||
[StoreKeys.LegacyFileBackupsLocation]: string
|
||||
}
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { BackupsDirectoryName } from '../Backups/BackupsManager'
|
||||
import { Language } from '../SpellcheckerManager'
|
||||
import { FileDoesNotExist } from '../Utils/FileUtils'
|
||||
import { ensureIsBoolean, isBoolean, isDev, isTesting } from '../Utils/Utils'
|
||||
import { ensureIsBoolean, isBoolean } from '../Utils/Utils'
|
||||
import { StoreData, StoreKeys } from './StoreKeys'
|
||||
import { app, logError } from './Store'
|
||||
import { logError } from './Store'
|
||||
|
||||
export function createSanitizedStoreData(data: any = {}): StoreData {
|
||||
return {
|
||||
[StoreKeys.MenuBarVisible]: ensureIsBoolean(data[StoreKeys.MenuBarVisible], true),
|
||||
[StoreKeys.UseSystemMenuBar]: ensureIsBoolean(data[StoreKeys.UseSystemMenuBar], false),
|
||||
[StoreKeys.BackupsDisabled]: ensureIsBoolean(data[StoreKeys.BackupsDisabled], false),
|
||||
[StoreKeys.MinimizeToTray]: ensureIsBoolean(data[StoreKeys.MinimizeToTray], false),
|
||||
[StoreKeys.EnableAutoUpdate]: ensureIsBoolean(data[StoreKeys.EnableAutoUpdate], true),
|
||||
[StoreKeys.UseNativeKeychain]: isBoolean(data[StoreKeys.UseNativeKeychain])
|
||||
? data[StoreKeys.UseNativeKeychain]
|
||||
: null,
|
||||
[StoreKeys.ExtServerHost]: data[StoreKeys.ExtServerHost],
|
||||
[StoreKeys.BackupsLocation]: sanitizeBackupsLocation(data[StoreKeys.BackupsLocation]),
|
||||
[StoreKeys.ZoomFactor]: sanitizeZoomFactor(data[StoreKeys.ZoomFactor]),
|
||||
[StoreKeys.SelectedSpellCheckerLanguageCodes]: sanitizeSpellCheckerLanguageCodes(
|
||||
data[StoreKeys.SelectedSpellCheckerLanguageCodes],
|
||||
),
|
||||
[StoreKeys.FileBackupsEnabled]: ensureIsBoolean(data[StoreKeys.FileBackupsEnabled], false),
|
||||
[StoreKeys.FileBackupsLocation]: data[StoreKeys.FileBackupsLocation],
|
||||
[StoreKeys.LastRunVersion]: data[StoreKeys.LastRunVersion],
|
||||
|
||||
[StoreKeys.LegacyTextBackupsLocation]: data[StoreKeys.LegacyTextBackupsLocation],
|
||||
[StoreKeys.LegacyTextBackupsDisabled]: data[StoreKeys.LegacyTextBackupsDisabled],
|
||||
[StoreKeys.LegacyFileBackupsEnabled]: data[StoreKeys.LegacyFileBackupsEnabled],
|
||||
[StoreKeys.LegacyFileBackupsLocation]: data[StoreKeys.LegacyFileBackupsLocation],
|
||||
}
|
||||
}
|
||||
function sanitizeZoomFactor(factor?: any): number {
|
||||
@@ -35,29 +34,7 @@ function sanitizeZoomFactor(factor?: any): number {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
function sanitizeBackupsLocation(location?: unknown): string {
|
||||
const defaultPath = path.join(
|
||||
isTesting() ? app.getPath('userData') : isDev() ? app.getPath('documents') : app.getPath('home'),
|
||||
BackupsDirectoryName,
|
||||
)
|
||||
|
||||
if (typeof location !== 'string') {
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = fs.lstatSync(location)
|
||||
if (stat.isDirectory()) {
|
||||
return location
|
||||
}
|
||||
/** Path points to something other than a directory */
|
||||
return defaultPath
|
||||
} catch (e) {
|
||||
/** Path does not point to a valid directory */
|
||||
logError(e)
|
||||
return defaultPath
|
||||
}
|
||||
}
|
||||
function sanitizeSpellCheckerLanguageCodes(languages?: unknown): Set<Language> | null {
|
||||
if (!languages) {
|
||||
return null
|
||||
|
||||
@@ -13,8 +13,6 @@ export function createEnglishStrings(): Strings {
|
||||
automaticUpdatesDisabled: 'Automatic Updates Disabled',
|
||||
disableAutomaticBackups: 'Disable Automatic Backups',
|
||||
enableAutomaticBackups: 'Enable Automatic Backups',
|
||||
changeBackupsLocation: 'Change Backups Location',
|
||||
openBackupsLocation: 'Open Backups Location',
|
||||
emailSupport: 'Email Support',
|
||||
website: 'Website',
|
||||
gitHub: 'GitHub',
|
||||
@@ -146,15 +144,5 @@ export function createEnglishStrings(): Strings {
|
||||
},
|
||||
unknownVersionName: 'Unknown',
|
||||
},
|
||||
backups: {
|
||||
errorChangingDirectory(error: any): string {
|
||||
return (
|
||||
'An error occurred while changing your backups directory. ' +
|
||||
'If this issue persists, please contact support with the following ' +
|
||||
'information: \n' +
|
||||
JSON.stringify(error)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,5 @@ export function createFrenchStrings(): Strings {
|
||||
},
|
||||
extensions: fallback.extensions,
|
||||
updates: fallback.updates,
|
||||
backups: {
|
||||
errorChangingDirectory(error: any): string {
|
||||
return (
|
||||
"Une erreur s'est produite lors du déplacement du dossier de " +
|
||||
'sauvegardes. Si le problème est récurrent, contactez le support ' +
|
||||
'technique (en anglais) avec les informations suivantes:\n' +
|
||||
JSON.stringify(error)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,6 @@ export function updates() {
|
||||
return str().updates
|
||||
}
|
||||
|
||||
export function backups() {
|
||||
return str().backups
|
||||
}
|
||||
|
||||
function stringsForLocale(locale: string): Strings {
|
||||
if (locale === 'en' || locale.startsWith('en-')) {
|
||||
return createEnglishStrings()
|
||||
|
||||
@@ -4,7 +4,6 @@ export interface Strings {
|
||||
tray: TrayStrings
|
||||
extensions: ExtensionsStrings
|
||||
updates: UpdateStrings
|
||||
backups: BackupsStrings
|
||||
}
|
||||
|
||||
interface AppMenuStrings {
|
||||
@@ -18,8 +17,6 @@ interface AppMenuStrings {
|
||||
automaticUpdatesDisabled: string
|
||||
disableAutomaticBackups: string
|
||||
enableAutomaticBackups: string
|
||||
changeBackupsLocation: string
|
||||
openBackupsLocation: string
|
||||
emailSupport: string
|
||||
website: string
|
||||
gitHub: string
|
||||
@@ -103,7 +100,3 @@ interface UpdateStrings {
|
||||
}
|
||||
unknownVersionName: string
|
||||
}
|
||||
|
||||
interface BackupsStrings {
|
||||
errorChangingDirectory(error: any): string
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ export const Paths = {
|
||||
get userDataDir(): string {
|
||||
return app.getPath('userData')
|
||||
},
|
||||
get homeDir(): string {
|
||||
return app.getPath('home')
|
||||
},
|
||||
get documentsDir(): string {
|
||||
return app.getPath('documents')
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import { compareVersions } from 'compare-versions'
|
||||
import { BrowserWindow, dialog, shell } from 'electron'
|
||||
import electronLog from 'electron-log'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
||||
import { action, computed, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../AppState'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { StoreKeys } from './Store/StoreKeys'
|
||||
import { updates as str } from './Strings'
|
||||
import { autoUpdatingAvailable } from './Types/Constants'
|
||||
@@ -84,7 +83,7 @@ export class UpdateState {
|
||||
|
||||
let updatesSetup = false
|
||||
|
||||
export function setupUpdates(window: BrowserWindow, appState: AppState, backupsManager: BackupsManagerInterface): void {
|
||||
export function setupUpdates(window: BrowserWindow, appState: AppState): void {
|
||||
if (!autoUpdatingAvailable) {
|
||||
return
|
||||
}
|
||||
@@ -97,22 +96,6 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
|
||||
|
||||
const updateState = appState.updates
|
||||
|
||||
function checkUpdateSafety(): boolean {
|
||||
let canUpdate: boolean
|
||||
if (appState.store.get(StoreKeys.BackupsDisabled)) {
|
||||
canUpdate = true
|
||||
} else {
|
||||
canUpdate = updateState.enableAutoUpdate && isLessThanOneHourFromNow(appState.lastBackupDate)
|
||||
}
|
||||
autoUpdater.autoInstallOnAppQuit = canUpdate
|
||||
autoUpdater.autoDownload = canUpdate
|
||||
return canUpdate
|
||||
}
|
||||
autorun(checkUpdateSafety)
|
||||
|
||||
const oneHour = 1 * 60 * 60 * 1000
|
||||
setInterval(checkUpdateSafety, oneHour)
|
||||
|
||||
autoUpdater.on('update-downloaded', (info: { version?: string }) => {
|
||||
window.webContents.send(MessageToWebApp.UpdateAvailable, null)
|
||||
updateState.autoUpdateHasBeenDownloaded(info.version || null)
|
||||
@@ -122,10 +105,9 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
|
||||
autoUpdater.on(MessageToWebApp.UpdateAvailable, (info: { version?: string }) => {
|
||||
updateState.checkedForUpdate(info.version || null)
|
||||
if (updateState.enableAutoUpdate) {
|
||||
const canUpdate = checkUpdateSafety()
|
||||
if (!canUpdate) {
|
||||
backupsManager.performBackup()
|
||||
}
|
||||
const canUpdate = updateState.enableAutoUpdate
|
||||
autoUpdater.autoInstallOnAppQuit = canUpdate
|
||||
autoUpdater.autoDownload = canUpdate
|
||||
}
|
||||
})
|
||||
autoUpdater.on('update-not-available', (info: { version?: string }) => {
|
||||
@@ -164,46 +146,21 @@ function quitAndInstall(window: BrowserWindow) {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function isLessThanOneHourFromNow(date: number | null) {
|
||||
const now = Date.now()
|
||||
const onHourMs = 1 * 60 * 60 * 1000
|
||||
return now - (date ?? 0) < onHourMs
|
||||
}
|
||||
|
||||
export async function showUpdateInstallationDialog(parentWindow: BrowserWindow, appState: AppState): Promise<void> {
|
||||
if (!appState.updates.latestVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
if (appState.lastBackupDate && isLessThanOneHourFromNow(appState.lastBackupDate)) {
|
||||
const result = await dialog.showMessageBox(parentWindow, {
|
||||
type: 'info',
|
||||
title: str().updateReady.title,
|
||||
message: str().updateReady.message(appState.updates.latestVersion),
|
||||
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
|
||||
cancelId: 0,
|
||||
})
|
||||
const result = await dialog.showMessageBox(parentWindow, {
|
||||
type: 'info',
|
||||
title: str().updateReady.title,
|
||||
message: str().updateReady.message(appState.updates.latestVersion),
|
||||
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
|
||||
cancelId: 0,
|
||||
})
|
||||
|
||||
const buttonIndex = result.response
|
||||
if (buttonIndex === 1) {
|
||||
quitAndInstall(parentWindow)
|
||||
}
|
||||
} else {
|
||||
const cancelId = 0
|
||||
const result = await dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: str().updateReady.title,
|
||||
message: str().updateReady.noRecentBackupMessage,
|
||||
detail: str().updateReady.noRecentBackupDetail(appState.lastBackupDate),
|
||||
checkboxLabel: str().updateReady.noRecentBackupChecbox,
|
||||
checkboxChecked: false,
|
||||
buttons: [str().updateReady.installLater, str().updateReady.quitAndInstall],
|
||||
cancelId,
|
||||
})
|
||||
|
||||
if (!result.checkboxChecked || result.response === cancelId) {
|
||||
return
|
||||
}
|
||||
const buttonIndex = result.response
|
||||
if (buttonIndex === 1) {
|
||||
quitAndInstall(parentWindow)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ export function debouncedJSONDiskWriter(durationMs: number, location: string, da
|
||||
}, durationMs)
|
||||
}
|
||||
|
||||
export async function openDirectoryPicker(): Promise<string | undefined> {
|
||||
export async function openDirectoryPicker(buttonLabel?: string): Promise<string | undefined> {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'showHiddenFiles', 'createDirectory'],
|
||||
buttonLabel: buttonLabel,
|
||||
})
|
||||
|
||||
return result.filePaths[0]
|
||||
@@ -63,6 +64,7 @@ export function writeJSONFileSync(filepath: string, data: unknown): void {
|
||||
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
/** Creates the directory if it doesn't exist. */
|
||||
export async function ensureDirectoryExists(dirPath: string): Promise<void> {
|
||||
try {
|
||||
const stat = await fs.promises.lstat(dirPath)
|
||||
@@ -251,7 +253,7 @@ export async function moveFiles(sources: string[], destDir: string): Promise<voi
|
||||
return Promise.all(sources.map((fileName) => moveFile(fileName, path.join(destDir, path.basename(fileName)))))
|
||||
}
|
||||
|
||||
async function moveFile(source: PathLike, destination: PathLike) {
|
||||
export async function moveFile(source: PathLike, destination: PathLike) {
|
||||
try {
|
||||
await fs.promises.rename(source, destination)
|
||||
} catch (_error) {
|
||||
@@ -261,6 +263,14 @@ async function moveFile(source: PathLike, destination: PathLike) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteFileIfExists(filePath: PathLike): Promise<void> {
|
||||
try {
|
||||
await deleteFile(filePath)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes a file, handling EPERM and EBUSY errors on Windows. */
|
||||
export async function deleteFile(filePath: PathLike): Promise<void> {
|
||||
for (let i = 1, maxTries = 10; i < maxTries; i++) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import path from 'path'
|
||||
import { AppMessageType, MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../AppState'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { createBackupsManager } from './Backups/BackupsManager'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { FilesBackupManager } from './FileBackups/FileBackupsManager'
|
||||
import { Keychain } from './Keychain/Keychain'
|
||||
import { MediaManager } from './Media/MediaManager'
|
||||
@@ -35,7 +33,6 @@ const WINDOW_MIN_HEIGHT = 400
|
||||
export interface WindowState {
|
||||
window: Electron.BrowserWindow
|
||||
menuManager: MenuManagerInterface
|
||||
backupsManager: BackupsManagerInterface
|
||||
trayManager: TrayManager
|
||||
}
|
||||
|
||||
@@ -64,7 +61,6 @@ export async function createWindowState({
|
||||
;(global as any).RemoteBridge = new RemoteBridge(
|
||||
window,
|
||||
Keychain,
|
||||
services.backupsManager,
|
||||
services.packageManager,
|
||||
services.searchManager,
|
||||
{
|
||||
@@ -93,7 +89,6 @@ export async function createWindowState({
|
||||
|
||||
window.on('blur', () => {
|
||||
window.webContents.send(MessageToWebApp.WindowBlurred, null)
|
||||
services.backupsManager.applicationDidBlur()
|
||||
})
|
||||
|
||||
window.once('ready-to-show', () => {
|
||||
@@ -201,8 +196,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
const searchManager = initializeSearchManager(window.webContents)
|
||||
initializeZoomManager(window, appState.store)
|
||||
|
||||
const backupsManager = createBackupsManager(window.webContents, appState)
|
||||
const updateManager = setupUpdates(window, appState, backupsManager)
|
||||
const updateManager = setupUpdates(window, appState)
|
||||
const trayManager = createTrayManager(window, appState.store)
|
||||
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
|
||||
const mediaManager = new MediaManager()
|
||||
@@ -214,16 +208,14 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
const menuManager = createMenuManager({
|
||||
appState,
|
||||
window,
|
||||
backupsManager,
|
||||
trayManager,
|
||||
store: appState.store,
|
||||
spellcheckerManager,
|
||||
})
|
||||
|
||||
const fileBackupsManager = new FilesBackupManager(appState)
|
||||
const fileBackupsManager = new FilesBackupManager(appState, window.webContents)
|
||||
|
||||
return {
|
||||
backupsManager,
|
||||
updateManager,
|
||||
trayManager,
|
||||
spellcheckerManager,
|
||||
|
||||
@@ -3,52 +3,22 @@ import { Component } from '../Main/Packages/PackageManagerInterface'
|
||||
|
||||
export interface CrossProcessBridge extends FileBackupsDevice {
|
||||
get extServerHost(): string
|
||||
|
||||
get useNativeKeychain(): boolean
|
||||
|
||||
get rendererPath(): string
|
||||
|
||||
get isMacOS(): boolean
|
||||
|
||||
get appVersion(): string
|
||||
|
||||
get useSystemMenuBar(): boolean
|
||||
|
||||
closeWindow(): void
|
||||
|
||||
minimizeWindow(): void
|
||||
|
||||
maximizeWindow(): void
|
||||
|
||||
unmaximizeWindow(): void
|
||||
|
||||
isWindowMaximized(): boolean
|
||||
|
||||
getKeychainValue(): Promise<unknown>
|
||||
|
||||
setKeychainValue: (value: unknown) => Promise<void>
|
||||
|
||||
clearKeychainValue(): Promise<boolean>
|
||||
|
||||
localBackupsCount(): Promise<number>
|
||||
|
||||
viewlocalBackups(): void
|
||||
|
||||
deleteLocalBackups(): Promise<void>
|
||||
|
||||
saveDataBackup(data: unknown): void
|
||||
|
||||
displayAppMenu(): void
|
||||
|
||||
syncComponents(components: Component[]): void
|
||||
|
||||
onMajorDataChange(): void
|
||||
|
||||
onSearch(text: string): void
|
||||
|
||||
onInitialDataLoad(): void
|
||||
|
||||
destroyAllData(): void
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
DesktopDeviceInterface,
|
||||
Environment,
|
||||
FileBackupsMapping,
|
||||
RawKeychainValue,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
FileBackupsMapping,
|
||||
PlaintextBackupsMapping,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
|
||||
import { Component } from '../Main/Packages/PackageManagerInterface'
|
||||
@@ -25,6 +25,33 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
super(appVersion)
|
||||
}
|
||||
|
||||
openLocation(path: string): Promise<void> {
|
||||
return this.remoteBridge.openLocation(path)
|
||||
}
|
||||
|
||||
presentDirectoryPickerForLocationChangeAndTransferOld(
|
||||
appendPath: string,
|
||||
oldLocation?: string | undefined,
|
||||
): Promise<string | undefined> {
|
||||
return this.remoteBridge.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
|
||||
}
|
||||
|
||||
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
|
||||
return this.remoteBridge.getFilesBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
|
||||
return this.remoteBridge.getPlaintextBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
persistPlaintextBackupsMappingFile(location: string): Promise<void> {
|
||||
return this.remoteBridge.persistPlaintextBackupsMappingFile(location)
|
||||
}
|
||||
|
||||
getTextBackupsCount(location: string): Promise<number> {
|
||||
return this.remoteBridge.getTextBackupsCount(location)
|
||||
}
|
||||
|
||||
async getKeychainValue() {
|
||||
if (this.useNativeKeychain) {
|
||||
const keychainValue = await this.remoteBridge.getKeychainValue()
|
||||
@@ -57,18 +84,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
this.remoteBridge.syncComponents(components)
|
||||
}
|
||||
|
||||
onMajorDataChange() {
|
||||
this.remoteBridge.onMajorDataChange()
|
||||
}
|
||||
|
||||
onSearch(text: string) {
|
||||
this.remoteBridge.onSearch(text)
|
||||
}
|
||||
|
||||
onInitialDataLoad() {
|
||||
this.remoteBridge.onInitialDataLoad()
|
||||
}
|
||||
|
||||
async clearAllDataFromDevice(workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> {
|
||||
await super.clearAllDataFromDevice(workspaceIdentifiers)
|
||||
|
||||
@@ -77,69 +96,36 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
return { killsApplication: true }
|
||||
}
|
||||
|
||||
async downloadBackup() {
|
||||
const receiver = window.webClient
|
||||
|
||||
receiver.didBeginBackup()
|
||||
|
||||
try {
|
||||
const data = await receiver.requestBackupFile()
|
||||
if (data) {
|
||||
this.remoteBridge.saveDataBackup(data)
|
||||
} else {
|
||||
receiver.didFinishBackup(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
receiver.didFinishBackup(false)
|
||||
}
|
||||
public isLegacyFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.remoteBridge.isLegacyFilesBackupsEnabled()
|
||||
}
|
||||
|
||||
async localBackupsCount() {
|
||||
return this.remoteBridge.localBackupsCount()
|
||||
public getLegacyFilesBackupsLocation(): Promise<string | undefined> {
|
||||
return this.remoteBridge.getLegacyFilesBackupsLocation()
|
||||
}
|
||||
|
||||
viewlocalBackups() {
|
||||
this.remoteBridge.viewlocalBackups()
|
||||
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean> {
|
||||
return this.remoteBridge.wasLegacyTextBackupsExplicitlyDisabled()
|
||||
}
|
||||
|
||||
async deleteLocalBackups() {
|
||||
return this.remoteBridge.deleteLocalBackups()
|
||||
getUserDocumentsDirectory(): Promise<string> {
|
||||
return this.remoteBridge.getUserDocumentsDirectory()
|
||||
}
|
||||
|
||||
public isFilesBackupsEnabled(): Promise<boolean> {
|
||||
return this.remoteBridge.isFilesBackupsEnabled()
|
||||
getLegacyTextBackupsLocation(): Promise<string | undefined> {
|
||||
return this.remoteBridge.getLegacyTextBackupsLocation()
|
||||
}
|
||||
|
||||
public enableFilesBackups(): Promise<void> {
|
||||
return this.remoteBridge.enableFilesBackups()
|
||||
saveTextBackupData(workspaceId: string, data: string): Promise<void> {
|
||||
return this.remoteBridge.saveTextBackupData(workspaceId, data)
|
||||
}
|
||||
|
||||
public disableFilesBackups(): Promise<void> {
|
||||
return this.remoteBridge.disableFilesBackups()
|
||||
}
|
||||
|
||||
public changeFilesBackupsLocation(): Promise<string | undefined> {
|
||||
return this.remoteBridge.changeFilesBackupsLocation()
|
||||
}
|
||||
|
||||
public getFilesBackupsLocation(): Promise<string> {
|
||||
return this.remoteBridge.getFilesBackupsLocation()
|
||||
}
|
||||
|
||||
async getFilesBackupsMappingFile(): Promise<FileBackupsMapping> {
|
||||
return this.remoteBridge.getFilesBackupsMappingFile()
|
||||
}
|
||||
|
||||
async openFilesBackupsLocation(): Promise<void> {
|
||||
return this.remoteBridge.openFilesBackupsLocation()
|
||||
}
|
||||
|
||||
openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
return this.remoteBridge.openFileBackup(record)
|
||||
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void> {
|
||||
return this.remoteBridge.savePlaintextNoteBackup(location, uuid, name, tags, data)
|
||||
}
|
||||
|
||||
async saveFilesBackupsFile(
|
||||
location: string,
|
||||
uuid: string,
|
||||
metaFile: string,
|
||||
downloadRequest: {
|
||||
@@ -148,17 +134,25 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'> {
|
||||
return this.remoteBridge.saveFilesBackupsFile(uuid, metaFile, downloadRequest)
|
||||
return this.remoteBridge.saveFilesBackupsFile(location, uuid, metaFile, downloadRequest)
|
||||
}
|
||||
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken> {
|
||||
return this.remoteBridge.getFileBackupReadToken(record)
|
||||
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken> {
|
||||
return this.remoteBridge.getFileBackupReadToken(filePath)
|
||||
}
|
||||
|
||||
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void> {
|
||||
return this.remoteBridge.migrateLegacyFileBackupsToNewStructure(newPath)
|
||||
}
|
||||
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse> {
|
||||
return this.remoteBridge.readNextChunk(token)
|
||||
}
|
||||
|
||||
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void> {
|
||||
return this.remoteBridge.monitorPlaintextBackupsLocationForChanges(backupsDirectory)
|
||||
}
|
||||
|
||||
async performHardReset(): Promise<void> {
|
||||
console.error('performHardReset is not yet implemented')
|
||||
}
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { ElectronMainEvents, MainEventHandler } from '../Shared/ElectronMainEvents'
|
||||
const { ipcRenderer } = require('electron')
|
||||
const RemoteBridge = require('@electron/remote').getGlobal('RemoteBridge')
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
type MainEventCallback = (event: IpcRendererEvent, value: any) => void
|
||||
|
||||
process.once('loaded', function () {
|
||||
contextBridge.exposeInMainWorld('electronRemoteBridge', RemoteBridge.exposableValue)
|
||||
|
||||
contextBridge.exposeInMainWorld('electronMainEvents', {
|
||||
handleUpdateAvailable: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, callback),
|
||||
const mainEvents: ElectronMainEvents = {
|
||||
setUpdateAvailableHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, handler),
|
||||
|
||||
handlePerformAutomatedBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, callback),
|
||||
setWindowBlurredHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowBlurred, handler),
|
||||
|
||||
handleFinishedSavingBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, callback),
|
||||
setWindowFocusedHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.WindowFocused, handler),
|
||||
|
||||
handleWindowBlurred: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowBlurred, callback),
|
||||
setWatchedDirectoriesChangeHandler: (handler: MainEventHandler) =>
|
||||
ipcRenderer.on(MessageToWebApp.WatchedDirectoriesChanges, handler),
|
||||
|
||||
handleWindowFocused: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowFocused, callback),
|
||||
setInstallComponentCompleteHandler: (handler: MainEventHandler) =>
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, handler),
|
||||
}
|
||||
|
||||
handleInstallComponentComplete: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, callback),
|
||||
})
|
||||
contextBridge.exposeInMainWorld('electronMainEvents', mainEvents)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { DesktopClientRequiresWebMethods } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import {
|
||||
DesktopClientRequiresWebMethods,
|
||||
DesktopWatchedDirectoriesChanges,
|
||||
} from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { StartApplication } from '@web/Application/Device/StartApplication'
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { CrossProcessBridge } from './CrossProcessBridge'
|
||||
import { DesktopDevice } from './DesktopDevice'
|
||||
import { ElectronMainEvents } from '../Shared/ElectronMainEvents'
|
||||
|
||||
declare const DEFAULT_SYNC_SERVER: string
|
||||
declare const WEBSOCKET_URL: string
|
||||
@@ -23,7 +27,7 @@ declare global {
|
||||
purchaseUrl: string
|
||||
startApplication: StartApplication
|
||||
zip: unknown
|
||||
electronMainEvents: any
|
||||
electronMainEvents: ElectronMainEvents
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,29 +118,36 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
if (isMacOS || useSystemMenuBar) {
|
||||
// !important is important here because #desktop-title-bar has display: flex.
|
||||
sheet.insertRule('#desktop-title-bar { display: none !important; }', sheet.cssRules.length)
|
||||
} else {
|
||||
/* Use custom title bar. Take the sn-titlebar-height off of
|
||||
the app content height so its not overflowing */
|
||||
sheet.insertRule(
|
||||
'[data-dialog] { position: relative; height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height) !important; }',
|
||||
sheet.cssRules.length,
|
||||
)
|
||||
sheet.insertRule(
|
||||
'[data-mobile-popover] { padding-top: var(--sn-desktop-titlebar-height) !important; }',
|
||||
sheet.cssRules.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
window.electronMainEvents.handleUpdateAvailable(() => {
|
||||
window.electronMainEvents.setUpdateAvailableHandler(() => {
|
||||
window.webClient.updateAvailable()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handlePerformAutomatedBackup(() => {
|
||||
void window.device.downloadBackup()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: { success: boolean }) => {
|
||||
window.webClient.didFinishBackup(data.success)
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowBlurred(() => {
|
||||
window.electronMainEvents.setWindowBlurredHandler(() => {
|
||||
window.webClient.windowLostFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowFocused(() => {
|
||||
window.electronMainEvents.setWindowFocusedHandler(() => {
|
||||
window.webClient.windowGainedFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleInstallComponentComplete((_: IpcRendererEvent, data: any) => {
|
||||
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: any) => {
|
||||
void window.webClient.onComponentInstallationComplete(data.component, undefined)
|
||||
})
|
||||
|
||||
window.electronMainEvents.setWatchedDirectoriesChangeHandler((_: IpcRendererEvent, changes: unknown) => {
|
||||
void window.webClient.handleWatchedDirectoriesChanges(changes as DesktopWatchedDirectoriesChanges)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
|
||||
export type MainEventHandler = (event: IpcRendererEvent, value: unknown) => void
|
||||
|
||||
export interface ElectronMainEvents {
|
||||
setUpdateAvailableHandler(handler: MainEventHandler): void
|
||||
setWindowBlurredHandler(handler: MainEventHandler): void
|
||||
setWindowFocusedHandler(handler: MainEventHandler): void
|
||||
setInstallComponentCompleteHandler(handler: MainEventHandler): void
|
||||
setWatchedDirectoriesChangeHandler(handler: MainEventHandler): void
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
export enum MessageToWebApp {
|
||||
UpdateAvailable = 'update-available',
|
||||
PerformAutomatedBackup = 'download-backup',
|
||||
FinishedSavingBackup = 'finished-saving-backup',
|
||||
WindowBlurred = 'window-blurred',
|
||||
WindowFocused = 'window-focused',
|
||||
InstallComponentComplete = 'install-component-complete',
|
||||
WatchedDirectoriesChanges = 'watched-directories-changes',
|
||||
}
|
||||
|
||||
export enum MessageToMainProcess {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.105.71",
|
||||
"version": "3.106.11",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
@@ -23,7 +23,7 @@
|
||||
"format": "prettier --write .",
|
||||
"lint:eslint": "eslint app/index.ts app/application.ts app/javascripts/**/*.ts",
|
||||
"lint:formatting": "prettier --check app",
|
||||
"lint": "yarn lint:formatting && yarn lint:eslint app",
|
||||
"lint": "yarn lint:formatting && yarn lint:eslint app && yarn tsc",
|
||||
"tsc": "tsc --noEmit",
|
||||
"release:mac": "node scripts/build.mjs mac",
|
||||
"start": "electron ./app --enable-logging --icon _icon/icon.png",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.30",
|
||||
"version": "1.21.33",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.59.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.59.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.59.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.59.1",
|
||||
"version": "1.59.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export enum FeatureIdentifier {
|
||||
CloudLink = 'org.standardnotes.cloudlink',
|
||||
DailyDropboxBackup = 'org.standardnotes.daily-dropbox-backup',
|
||||
DailyEmailBackup = 'org.standardnotes.daily-email-backup',
|
||||
DailyGDriveBackup = 'org.standardnotes.daily-gdrive-backup',
|
||||
DailyOneDriveBackup = 'org.standardnotes.daily-onedrive-backup',
|
||||
Files = 'org.standardnotes.files',
|
||||
FilesLowStorageTier = 'org.standardnotes.files-low-storage-tier',
|
||||
FilesMaximumStorageTier = 'org.standardnotes.files-max-storage-tier',
|
||||
|
||||
@@ -41,21 +41,6 @@ export function serverFeatures(): ServerFeatureDescription[] {
|
||||
identifier: FeatureIdentifier.SignInAlerts,
|
||||
permission_name: PermissionName.SignInAlerts,
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
identifier: FeatureIdentifier.DailyDropboxBackup,
|
||||
permission_name: PermissionName.DailyDropboxBackup,
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
identifier: FeatureIdentifier.DailyGDriveBackup,
|
||||
permission_name: PermissionName.DailyGDriveBackup,
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
identifier: FeatureIdentifier.DailyOneDriveBackup,
|
||||
permission_name: PermissionName.DailyOneDriveBackup,
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.ProPlan],
|
||||
identifier: FeatureIdentifier.FilesMaximumStorageTier,
|
||||
|
||||
@@ -2,14 +2,10 @@ export enum PermissionName {
|
||||
AccountSwitcher = 'app:account-switcher',
|
||||
AutobiographyTheme = 'theme:autobiography',
|
||||
BoldEditor = 'editor:bold',
|
||||
CloudLink = 'component:cloud-link',
|
||||
CodeEditor = 'editor:code-editor',
|
||||
ComponentFilesafe = 'component:filesafe',
|
||||
ComponentFolders = 'component:folders',
|
||||
DailyDropboxBackup = 'server:daily-dropbox-backup',
|
||||
DailyEmailBackup = 'server:daily-email-backup',
|
||||
DailyGDriveBackup = 'server:daily-gdrive-backup',
|
||||
DailyOneDriveBackup = 'server:daily-onedrive-backup',
|
||||
Files = 'app:files',
|
||||
FilesMaximumStorageTier = 'server:files-max-storage-tier',
|
||||
FilesLowStorageTier = 'server:files-low-storage-tier',
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.35",
|
||||
"version": "1.28.40",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* automatically convert Super notes to Markdown for the Plaintext Backups feature ([75ecf4c](https://github.com/standardnotes/app/commit/75ecf4c393a6bc0ab2d6bf50dcbee21cef520746))
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
|
||||
|
||||
## [1.14.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.45",
|
||||
"version": "1.15.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export type DesktopWatchedDirectoriesChange = {
|
||||
itemUuid: string
|
||||
path: string
|
||||
type: 'rename' | 'change'
|
||||
content: string
|
||||
}
|
||||
|
||||
export type DesktopWatchedDirectoriesChanges = DesktopWatchedDirectoriesChange[]
|
||||
@@ -1,12 +1,44 @@
|
||||
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
|
||||
import { FileBackupRecord, FileBackupsMapping } from './FileBackupsMapping'
|
||||
import { FileBackupsMapping } from './FileBackupsMapping'
|
||||
|
||||
type PlaintextNoteRecord = {
|
||||
tag?: string
|
||||
path: string
|
||||
}
|
||||
|
||||
type UuidString = string
|
||||
export type PlaintextBackupsMapping = {
|
||||
version: string
|
||||
files: Record<UuidString, PlaintextNoteRecord[]>
|
||||
}
|
||||
|
||||
export interface FileBackupsDevice
|
||||
extends FileBackupsMethods,
|
||||
LegacyBackupsMethods,
|
||||
PlaintextBackupsMethods,
|
||||
TextBackupsMethods {
|
||||
openLocation(path: string): Promise<void>
|
||||
|
||||
/**
|
||||
* The reason we combine presenting a directory picker and transfering old files to the new location
|
||||
* in one function is so we don't have to expose a general `transferDirectories` function to the web app,
|
||||
* which would give it too much power.
|
||||
* @param appendPath The path to append to the selected directory.
|
||||
*/
|
||||
presentDirectoryPickerForLocationChangeAndTransferOld(
|
||||
appendPath: string,
|
||||
oldLocation?: string,
|
||||
): Promise<string | undefined>
|
||||
|
||||
monitorPlaintextBackupsLocationForChanges(backupsDirectory: string): Promise<void>
|
||||
}
|
||||
|
||||
export type FileBackupReadToken = string
|
||||
export type FileBackupReadChunkResponse = { chunk: Uint8Array; isLast: boolean; progress: FileDownloadProgress }
|
||||
|
||||
export interface FileBackupsDevice {
|
||||
getFilesBackupsMappingFile(): Promise<FileBackupsMapping>
|
||||
interface FileBackupsMethods {
|
||||
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping>
|
||||
saveFilesBackupsFile(
|
||||
location: string,
|
||||
uuid: string,
|
||||
metaFile: string,
|
||||
downloadRequest: {
|
||||
@@ -15,13 +47,26 @@ export interface FileBackupsDevice {
|
||||
url: string
|
||||
},
|
||||
): Promise<'success' | 'failed'>
|
||||
getFileBackupReadToken(record: FileBackupRecord): Promise<FileBackupReadToken>
|
||||
getFileBackupReadToken(filePath: string): Promise<FileBackupReadToken>
|
||||
readNextChunk(token: string): Promise<FileBackupReadChunkResponse>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
changeFilesBackupsLocation(): Promise<string | undefined>
|
||||
getFilesBackupsLocation(): Promise<string>
|
||||
openFilesBackupsLocation(): Promise<void>
|
||||
openFileBackup(record: FileBackupRecord): Promise<void>
|
||||
}
|
||||
|
||||
interface PlaintextBackupsMethods {
|
||||
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping>
|
||||
persistPlaintextBackupsMappingFile(location: string): Promise<void>
|
||||
savePlaintextNoteBackup(location: string, uuid: string, name: string, tags: string[], data: string): Promise<void>
|
||||
}
|
||||
|
||||
interface TextBackupsMethods {
|
||||
getTextBackupsCount(location: string): Promise<number>
|
||||
saveTextBackupData(location: string, data: string): Promise<void>
|
||||
getUserDocumentsDirectory(): Promise<string>
|
||||
}
|
||||
|
||||
interface LegacyBackupsMethods {
|
||||
migrateLegacyFileBackupsToNewStructure(newPath: string): Promise<void>
|
||||
isLegacyFilesBackupsEnabled(): Promise<boolean>
|
||||
getLegacyFilesBackupsLocation(): Promise<string | undefined>
|
||||
wasLegacyTextBackupsExplicitlyDisabled(): Promise<boolean>
|
||||
getLegacyTextBackupsLocation(): Promise<string | undefined>
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { FileBackupsConstantsV1 } from './FileBackupsConstantsV1'
|
||||
|
||||
export type FileBackupRecord = {
|
||||
backedUpOn: Date
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
|
||||
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
import { OnChunkCallback } from '../Chunker/OnChunkCallback'
|
||||
import { DesktopWatchedDirectoriesChanges } from '../Device/DesktopWatchedChanges'
|
||||
import { FileBackupRecord } from '../Device/FileBackupsMapping'
|
||||
import { SuperConverterServiceInterface } from './SuperConverterServiceInterface'
|
||||
|
||||
export interface BackupServiceInterface {
|
||||
openAllDirectoriesContainingBackupFiles(): void
|
||||
prependWorkspacePathForPath(path: string): string
|
||||
importWatchedDirectoryChanges(changes: DesktopWatchedDirectoriesChanges): Promise<void>
|
||||
setSuperConverter(converter: SuperConverterServiceInterface): void
|
||||
|
||||
getFileBackupInfo(file: { uuid: string }): Promise<FileBackupRecord | undefined>
|
||||
readEncryptedFileFromBackup(uuid: string, onChunk: OnChunkCallback): Promise<'success' | 'failed' | 'aborted'>
|
||||
isFilesBackupsEnabled(): Promise<boolean>
|
||||
isFilesBackupsEnabled(): boolean
|
||||
enableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): Promise<void>
|
||||
disableFilesBackups(): void
|
||||
changeFilesBackupsLocation(): Promise<string | undefined>
|
||||
getFilesBackupsLocation(): Promise<string>
|
||||
getFilesBackupsLocation(): string | undefined
|
||||
openFilesBackupsLocation(): Promise<void>
|
||||
openFileBackup(record: FileBackupRecord): Promise<void>
|
||||
getFileBackupAbsolutePath(record: FileBackupRecord): string
|
||||
|
||||
isTextBackupsEnabled(): boolean
|
||||
enableTextBackups(): Promise<void>
|
||||
disableTextBackups(): void
|
||||
getTextBackupsLocation(): string | undefined
|
||||
openTextBackupsLocation(): Promise<void>
|
||||
changeTextBackupsLocation(): Promise<string | undefined>
|
||||
saveTextBackupData(data: string): Promise<void>
|
||||
|
||||
isPlaintextBackupsEnabled(): boolean
|
||||
enablePlaintextBackups(): Promise<void>
|
||||
disablePlaintextBackups(): void
|
||||
getPlaintextBackupsLocation(): string | undefined
|
||||
openPlaintextBackupsLocation(): Promise<void>
|
||||
changePlaintextBackupsLocation(): Promise<string | undefined>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SuperConverterServiceInterface {
|
||||
convertString: (superString: string, toFormat: 'txt' | 'md' | 'html' | 'json') => string
|
||||
}
|
||||
@@ -1,30 +1,32 @@
|
||||
export * from './Api/DirectoryHandle'
|
||||
export * from './Api/FileHandleRead'
|
||||
export * from './Api/FileHandleReadWrite'
|
||||
export * from './Api/FilesApiInterface'
|
||||
export * from './Api/FileSystemApi'
|
||||
export * from './Api/FileSystemNoSelection'
|
||||
export * from './Api/FileSystemResult'
|
||||
export * from './Api/FilesApiInterface'
|
||||
export * from './Cache/FileMemoryCache'
|
||||
export * from './Chunker/ByteChunker'
|
||||
export * from './Chunker/OnChunkCallback'
|
||||
export * from './Chunker/OrderedByteChunker'
|
||||
export * from './Device/DesktopWatchedChanges'
|
||||
export * from './Device/FileBackupMetadataFile'
|
||||
export * from './Device/FileBackupsConstantsV1'
|
||||
export * from './Device/FileBackupsDevice'
|
||||
export * from './Device/FileBackupsMapping'
|
||||
export * from './Operations/DownloadAndDecrypt'
|
||||
export * from './Operations/EncryptAndUpload'
|
||||
export * from './Service/BackupServiceInterface'
|
||||
export * from './Service/SuperConverterServiceInterface'
|
||||
export * from './Service/FilesClientInterface'
|
||||
export * from './Service/ReadAndDecryptBackupFileFileSystemAPI'
|
||||
export * from './Service/ReadAndDecryptBackupFileUsingBackupService'
|
||||
export * from './Operations/DownloadAndDecrypt'
|
||||
export * from './Operations/EncryptAndUpload'
|
||||
export * from './UseCase/FileDecryptor'
|
||||
export * from './UseCase/FileUploader'
|
||||
export * from './UseCase/FileEncryptor'
|
||||
export * from './UseCase/FileDownloader'
|
||||
export * from './Types/DecryptedBytes'
|
||||
export * from './Types/EncryptedBytes'
|
||||
export * from './Types/FileDownloadProgress'
|
||||
export * from './Types/FileUploadProgress'
|
||||
export * from './Types/FileUploadResult'
|
||||
export * from './UseCase/FileDecryptor'
|
||||
export * from './UseCase/FileDownloader'
|
||||
export * from './UseCase/FileEncryptor'
|
||||
export * from './UseCase/FileUploader'
|
||||
|
||||
@@ -3,6 +3,128 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.53.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.53.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.53.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. ([#2322](https://github.com/standardnotes/app/issues/2322)) ([7e3db49](https://github.com/standardnotes/app/commit/7e3db49322034aa644c3c99ccf9a5f1b508583aa))
|
||||
|
||||
## [3.52.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.52.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (6.1.5)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
@@ -9,28 +9,28 @@ GEM
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.603.0)
|
||||
aws-sdk-core (3.131.2)
|
||||
aws-partitions (1.755.0)
|
||||
aws-sdk-core (3.171.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.57.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (1.63.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-s3 (1.121.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
@@ -81,13 +81,13 @@ GEM
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.92.3)
|
||||
faraday (1.10.0)
|
||||
excon (0.99.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -116,7 +116,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.207.0)
|
||||
fastlane (2.212.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -160,9 +160,9 @@ GEM
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.24.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-core (0.7.0)
|
||||
google-apis-androidpublisher_v3 (0.40.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -171,27 +171,27 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.13.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.10.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-storage_v1 (0.17.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.37.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.2.0)
|
||||
googleauth (1.5.2)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -204,11 +204,11 @@ GEM
|
||||
httpclient (2.8.3)
|
||||
i18n (1.10.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.4.1)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.15.0)
|
||||
molinillo (0.8.0)
|
||||
@@ -220,7 +220,7 @@ GEM
|
||||
netrc (0.11.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
plist (3.7.0)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
@@ -239,7 +239,7 @@ GEM
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
@@ -259,7 +259,7 @@ GEM
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcode-install (2.8.0)
|
||||
claide (>= 0.9.1, < 1.1.0)
|
||||
@@ -291,4 +291,4 @@ RUBY VERSION
|
||||
ruby 2.7.6p219
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.25
|
||||
2.3.15
|
||||
|
||||
@@ -34,7 +34,7 @@ platform :ios do
|
||||
build_number
|
||||
end
|
||||
|
||||
def deploy_ios(scheme)
|
||||
def deploy_ios(scheme, is_testflight = false)
|
||||
sign_ios
|
||||
build_number = build_ios(scheme)
|
||||
app_store_connect_api_key(
|
||||
@@ -55,7 +55,7 @@ platform :ios do
|
||||
changelog: 'Fixes and improvements.'
|
||||
)
|
||||
|
||||
if is_prod
|
||||
if is_prod && !is_testflight
|
||||
upload_to_app_store(
|
||||
build_number: build_number,
|
||||
submit_for_review: true,
|
||||
@@ -83,6 +83,11 @@ platform :ios do
|
||||
deploy_ios 'StandardNotes'
|
||||
end
|
||||
|
||||
desc 'Deploy prod app to tesflight'
|
||||
lane :testflight do
|
||||
deploy_ios 'StandardNotes', true
|
||||
end
|
||||
|
||||
desc 'Setup iOS for running on CI'
|
||||
lane :setup do
|
||||
setup_project
|
||||
|
||||
@@ -16,7 +16,14 @@
|
||||
window.plansUrl = "https://standardnotes.com/plans";
|
||||
window.dashboardUrl = "https://standardnotes.com/dashboard";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window.onerror = function (message, source, lineno, colno, error) {
|
||||
if (document.readyState === "complete") {
|
||||
return
|
||||
}
|
||||
window.ReactNativeWebView.postMessage("appLoadError");
|
||||
};
|
||||
</script>
|
||||
<script src="web-src/app.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -637,7 +637,7 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost: 57d2868c099736d80fcd648bf211b4431e51a558
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: 60195509584153283780abdac5569feffb8f08cc
|
||||
@@ -658,7 +658,7 @@ SPEC CHECKSUMS:
|
||||
MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd
|
||||
MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
|
||||
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
|
||||
RCTRequired: bec48f07daf7bcdc2655a0cde84e07d24d2a9e2a
|
||||
RCTTypeSafety: 171394eebacf71e1cfad79dbfae7ee8fc16ca80a
|
||||
React: d7433ccb6a8c36e4cbed59a73c0700fc83c3e98a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.52.18",
|
||||
"version": "3.53.11",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Keyboard, Platform } from 'react-native'
|
||||
import { Button, Keyboard, Platform, Text, View } 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'
|
||||
@@ -34,6 +34,8 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
[androidBackHandlerService, colorSchemeService, stateService],
|
||||
)
|
||||
|
||||
const [showAndroidWebviewUpdatePrompt, setShowAndroidWebviewUpdatePrompt] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const removeStateServiceListener = stateService.addEventObserver((event: ReactNativeToWebEvent) => {
|
||||
webViewRef.current?.postMessage(JSON.stringify({ reactNativeEvent: event, messageType: 'event' }))
|
||||
@@ -228,6 +230,10 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
|
||||
const onMessage = (event: WebViewMessageEvent) => {
|
||||
const message = event.nativeEvent.data
|
||||
if (message === 'appLoadError' && Platform.OS === 'android') {
|
||||
setShowAndroidWebviewUpdatePrompt(true)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const functionData = JSON.parse(message)
|
||||
void onFunctionMessage(functionData.functionName, functionData.messageId, functionData.args)
|
||||
@@ -277,6 +283,47 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
const requireInlineMediaPlaybackForMomentsFeature = true
|
||||
const requireMediaUserInteractionForMomentsFeature = false
|
||||
|
||||
if (showAndroidWebviewUpdatePrompt) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
Could not load app
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
marginBottom: 20,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Please make sure your Android System Webview is updated to the latest version
|
||||
</Text>
|
||||
<Button
|
||||
title={'Update'}
|
||||
onPress={() => {
|
||||
setShowAndroidWebviewUpdatePrompt(false)
|
||||
device.openUrl('https://play.google.com/store/apps/details?id=com.google.android.webview')
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.45.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
### Features
|
||||
|
||||
* Replaced margin resizers with "Editor width" options. You can set it globally from Preferences > Appearance or per-note from the note options menu ([#2324](https://github.com/standardnotes/app/issues/2324)) ([9fbb845](https://github.com/standardnotes/app/commit/9fbb845b1d3b1ca6a4b064f3b63bdae1cf7777e8))
|
||||
|
||||
## [1.44.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.44.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.44.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.44.1",
|
||||
"version": "1.45.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||
import { NoteContent, NoteContentSpecialized } from './NoteContent'
|
||||
import { EditorLineWidth } from '../UserPrefs'
|
||||
|
||||
export const isNote = (x: ItemInterface): x is SNNote => x.content_type === ContentType.Note
|
||||
|
||||
@@ -15,6 +16,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
||||
public readonly preview_plain: string
|
||||
public readonly preview_html: string
|
||||
public readonly spellcheck?: boolean
|
||||
public readonly editorWidth?: EditorLineWidth
|
||||
public readonly noteType?: NoteType
|
||||
public readonly authorizedForListed: boolean
|
||||
|
||||
@@ -30,6 +32,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
||||
this.preview_plain = String(this.payload.content.preview_plain || '')
|
||||
this.preview_html = String(this.payload.content.preview_html || '')
|
||||
this.spellcheck = this.payload.content.spellcheck
|
||||
this.editorWidth = this.payload.content.editorWidth
|
||||
this.noteType = this.payload.content.noteType
|
||||
this.editorIdentifier = this.payload.content.editorIdentifier
|
||||
this.authorizedForListed = this.payload.content.authorizedForListed || false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { EditorLineWidth } from '../UserPrefs'
|
||||
|
||||
export interface NoteContentSpecialized {
|
||||
title: string
|
||||
@@ -8,6 +9,7 @@ export interface NoteContentSpecialized {
|
||||
preview_plain?: string
|
||||
preview_html?: string
|
||||
spellcheck?: boolean
|
||||
editorWidth?: EditorLineWidth
|
||||
noteType?: NoteType
|
||||
editorIdentifier?: FeatureIdentifier | string
|
||||
authorizedForListed?: boolean
|
||||
|
||||
@@ -5,6 +5,7 @@ import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReferenc
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ContentReferenceType } from '../../Abstract/Item'
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { EditorLineWidth } from '../UserPrefs'
|
||||
|
||||
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||
set title(title: string) {
|
||||
@@ -31,6 +32,10 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||
this.mutableContent.spellcheck = spellcheck
|
||||
}
|
||||
|
||||
set editorWidth(editorWidth: EditorLineWidth) {
|
||||
this.mutableContent.editorWidth = editorWidth
|
||||
}
|
||||
|
||||
set noteType(noteType: NoteType) {
|
||||
this.mutableContent.noteType = noteType
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum PrefKey {
|
||||
EditorSpellcheck = 'spellcheck',
|
||||
EditorResizersEnabled = 'marginResizersEnabled',
|
||||
EditorLineHeight = 'editorLineHeight',
|
||||
EditorLineWidth = 'editorLineWidth',
|
||||
EditorFontSize = 'editorFontSize',
|
||||
SortNotesBy = 'sortBy',
|
||||
SortNotesReverse = 'sortReverse',
|
||||
@@ -65,6 +66,13 @@ export enum EditorLineHeight {
|
||||
Loose = 'Loose',
|
||||
}
|
||||
|
||||
export enum EditorLineWidth {
|
||||
Narrow = 'Narrow',
|
||||
Wide = 'Wide',
|
||||
Dynamic = 'Dynamic',
|
||||
FullWidth = 'FullWidth',
|
||||
}
|
||||
|
||||
export enum EditorFontSize {
|
||||
ExtraSmall = 'ExtraSmall',
|
||||
Small = 'Small',
|
||||
@@ -107,6 +115,7 @@ export type PrefValue = {
|
||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
|
||||
[PrefKey.CustomNoteTitleFormat]: string
|
||||
[PrefKey.EditorLineHeight]: EditorLineHeight
|
||||
[PrefKey.EditorLineWidth]: EditorLineWidth
|
||||
[PrefKey.EditorFontSize]: EditorFontSize
|
||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||
[PrefKey.DarkMode]: boolean
|
||||
|
||||
@@ -3,6 +3,126 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.279](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.278](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.277](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.276](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.275](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.274](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.273](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.272](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.271](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.270](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.269](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.268](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.267](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.266](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.265](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-05-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.264](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.263](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.262](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.261](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.260](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.259](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.258](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.257](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.256](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.255](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.254](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.253](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.252](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.251](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.250](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.249](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.249",
|
||||
"version": "1.4.279",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.13.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user