Compare commits

...
Author SHA1 Message Date
StandardNotes CI 8ac51afa76 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
2026-07-18 19:44:57 +00:00
Antonella Sgarlatta eec6bc2782 fix: Fixes text alignment in Super linked items (#3027) 2026-07-18 16:38:45 -03:00
Antonella Sgarlatta ee56c858ab fix: Fix keyboard shortcut handling from within iframe editors (#3025) 2026-07-18 16:37:47 -03:00
Antonella Sgarlatta 44a1a6c374 fix: Fixes incorrect rendering of some special characters on Super PDF export (#3024) 2026-07-18 16:37:26 -03:00
Antonella Sgarlatta 883cd4fda9 fix: Fixes gaps after linked items within bullet lists on Super PDF export (#3023) 2026-07-18 16:37:07 -03:00
Antonella Sgarlatta 981a8149f5 chore: add mobile beta workflow [skip ci] 2026-07-17 12:38:07 -03:00
StandardNotes CI 4e3aa7d69f chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
2026-07-06 17:27:09 +00:00
Antonella Sgarlatta 8a20206cf3 fix: Allows demo login on demo host only (#3021) 2026-07-06 14:21:19 -03:00
Antonella Sgarlatta 386d73ffb8 fix: Fixes Super replace functionality not correctly hidden when toggling Prevent editing on a note (#3022) 2026-07-06 14:21:02 -03:00
Michael Lee 9d026df49a fix: Fixes default language for newly created code blocks in Super notes (#2999) 2026-07-06 14:20:39 -03:00
Antonella Sgarlatta 089bc421fc chore: fixes unsigned build workflow [skip ci] 2026-06-25 13:48:54 -03:00
Antonella Sgarlatta 29223e96c0 chore: fixes unsigned build workflow [skip ci] 2026-06-25 01:24:28 -03:00
Antonella Sgarlatta 12503f7c5a chore: remove rebuild modules step from manual build workflow [skip ci] 2026-06-24 23:57:53 -03:00
Antonella Sgarlatta 195d1a6233 chore: add workflows for manual builds [skip ci] 2026-06-24 15:50:09 -03:00
StandardNotes CI 3d8bae26e8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
2026-06-08 03:51:58 +00:00
Antonella Sgarlatta fe4d0aacbf chore: temporarily disallow android purchases (#3019) 2026-06-08 00:45:49 -03:00
StandardNotes CI ed1cd0c366 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2026-06-07 03:12:16 +00:00
Antonella Sgarlatta c2bdda2ccb chore: temporarily remove manage sub button for android 2026-06-07 00:06:33 -03:00
StandardNotes CI 09ffb07909 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
2026-06-05 18:01:22 +00:00
Antonella Sgarlatta 81bd2de425 chore: temporarily disallow android purchases (#3018) 2026-06-05 14:55:13 -03:00
Antonella Sgarlatta f4885188ad fix: Fixes checkboxes parsed as bullet items when importing Evernote notes 2026-06-05 14:55:03 -03:00
Antonella Sgarlatta a62f496ee6 fix: Fixes highlight text style lost when importing Evernote notes 2026-06-05 14:55:03 -03:00
Antonella Sgarlatta b4faa20ac1 fix: Fixes empty lines doubled when importing Evernote notes 2026-06-05 14:55:03 -03:00
Antonella Sgarlatta 6c07fab9e9 chore: first ttag batch (#3007) [skip ci]
* chore: set ttag for sign in and sign up strings

* chore: set ttag for account menu and session actions

* chore: set ttag for password change and re-encryption

* chore: set ttag for import and export account data
2026-05-28 14:37:02 -03:00
StandardNotes CI 61bf8ca2f6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2026-05-27 04:43:22 +00:00
Antonella Sgarlatta 80fa337afb fix: Fixes images rendering too small on Super pdf export 2026-05-27 01:38:20 -03:00
Antonella Sgarlatta cd7046bb69 chore: upgrade react-pdf 2026-05-27 01:38:20 -03:00
Antonella Sgarlatta c81589b945 fix: Fixes cell padding and bold font within table cells in Super pdf export 2026-05-27 01:38:20 -03:00
Antonella Sgarlatta bd3cf600e7 fix: Fixes Super pdf export code block formatting issues 2026-05-27 01:38:20 -03:00
Antonella Sgarlatta e22e6fce79 fix: Fixes Super pdf export line height 2026-05-27 01:38:20 -03:00
StandardNotes CI 6daf58c928 fix: Fixes Super pdf export headings font sizes and weights 2026-05-27 01:38:20 -03:00
Antonella Sgarlatta eda1bfe0bd fix: Fixes Upgrade button in Settings leading to purchase page (#3011) 2026-05-27 01:37:37 -03:00
Antonella Sgarlatta 7ce6e75bc7 chore: add validation rules for package identifiers (#3012) 2026-05-27 01:37:22 -03:00
Antonella Sgarlatta a5984ae5b4 chore: correct default version for mobile dev app [skip ci] 2026-05-13 00:55:35 -03:00
143 changed files with 2259 additions and 493 deletions
@@ -0,0 +1,19 @@
name: Desktop Manual Build
on:
workflow_dispatch:
inputs:
platform:
description: Platform to build
required: true
type: choice
options:
- mac
- linux
- windows
jobs:
build:
uses: ./.github/workflows/desktop.build.reuse.yml
with:
platform: ${{ inputs.platform }}
+254
View File
@@ -0,0 +1,254 @@
name: Desktop Reusable Manual Build Workflow
on:
workflow_call:
inputs:
platform:
required: true
type: string
jobs:
Mac:
if: inputs.platform == 'mac'
runs-on: macos-latest
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: yarn install --immutable
- name: Rebuild Electron Native Modules
run: yarn workspace @standardnotes/desktop rebuild:home-server
- run: yarn build:desktop
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: Compile Mac
run: yarn run webpack --config desktop.webpack.prod.js
- name: MacX64
run: |
yarn run electron-builder --mac --x64 --publish=never --config electron-builder.unsigned.cjs --c.extraMetadata.version=${{ env.APP_VERSION }}
node scripts/fixMacZip.js ${{ env.APP_VERSION }}
- name: MacArm64
run: yarn run electron-builder --mac --arm64 --publish=never --config electron-builder.unsigned.cjs --c.extraMetadata.version=${{ env.APP_VERSION }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-macos
retention-days: 7
path: |
packages/desktop/dist/*.dmg
packages/desktop/dist/*.zip
packages/desktop/dist/*.blockmap
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
Linux-AppImage-X64:
name: Linux AppImage X64
if: inputs.platform == 'linux'
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install FPM
run: sudo gem install fpm -f
- run: yarn install --immutable
- name: Rebuild Electron Native Modules
run: yarn workspace @standardnotes/desktop rebuild:home-server
- run: yarn build:desktop
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: Compile for AppImage
run: yarn run webpack --config desktop.webpack.prod.js
- name: AppImageX64
run: yarn run electron-builder --linux --x64 -c.linux.target=AppImage --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-linux-appimage-x64
retention-days: 7
path: |
packages/desktop/dist/*.AppImage
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
Linux-AppImage-ARM64:
name: Linux AppImage ARM64
if: inputs.platform == 'linux'
runs-on: ubuntu-24.04-arm
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install FPM
run: sudo gem install fpm -f
- run: yarn install --immutable
- name: Rebuild Electron Native Modules
run: yarn workspace @standardnotes/desktop rebuild:home-server
- run: yarn build:desktop
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: Compile for AppImage
run: yarn run webpack --config desktop.webpack.prod.js
- name: AppImageArm64
run: yarn run electron-builder --linux --arm64 -c.linux.target=AppImage --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-linux-appimage-arm64
retention-days: 7
path: |
packages/desktop/dist/*.AppImage
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
Linux-Deb-X64:
name: Linux Deb X64
if: inputs.platform == 'linux'
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install FPM
run: sudo gem install fpm -f
- run: yarn install --immutable
- name: Rebuild Electron Native Modules
run: yarn workspace @standardnotes/desktop rebuild:home-server
- run: yarn build:desktop
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: Deb
run: |
yarn run webpack --config desktop.webpack.prod.js --env deb
yarn run electron-builder --linux --x64 -c.linux.target=deb --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-linux-deb-x64
retention-days: 7
path: |
packages/desktop/dist/*.deb
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
Linux-Deb-ARM64:
name: Linux Deb ARM64
if: inputs.platform == 'linux'
runs-on: ubuntu-24.04-arm
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install FPM
run: sudo gem install fpm -f
- run: yarn install --immutable
- name: Rebuild Electron Native Modules
run: yarn workspace @standardnotes/desktop rebuild:home-server
- run: yarn build:desktop
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: DebArm64
env:
USE_SYSTEM_FPM: 'true'
run: |
yarn run webpack --config desktop.webpack.prod.js --env deb
yarn run electron-builder --linux --arm64 -c.linux.target=deb --publish=never --c.extraMetadata.version=${{ env.APP_VERSION }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-linux-deb-arm64
retention-days: 7
path: |
packages/desktop/dist/*.deb
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
Windows:
name: Windows
if: inputs.platform == 'windows'
runs-on: windows-latest
env:
NODE_OPTIONS: --max-old-space-size=8192
CSC_IDENTITY_AUTO_DISCOVERY: false
defaults:
run:
working-directory: packages/desktop
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: corepack enable
- run: yarn install --immutable
- run: yarn build:desktop
- name: Set app version
shell: bash
run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- name: Compile
run: yarn run webpack --config desktop.webpack.prod.js
- name: Build Windows installers
shell: bash
run: yarn run electron-builder --windows --x64 --ia32 --publish=never --config electron-builder.unsigned.cjs --c.extraMetadata.version=$APP_VERSION
- name: Upload
uses: actions/upload-artifact@v4
with:
name: dist-windows
retention-days: 7
path: |
packages/desktop/dist/*.exe
packages/desktop/dist/*.yml
packages/desktop/dist/*.yaml
packages/desktop/dist/*.blockmap
@@ -0,0 +1,48 @@
name: Mobile Release Closed Beta
on:
workflow_dispatch:
jobs:
android:
defaults:
run:
working-directory: packages/mobile
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Java version
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Export version from package.json
run:
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
- name: Setup react-native kernel and increase watchers
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
- name: Decode Production Android keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE }}" > keystore.keystore.asc
gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.keystore.asc > android/app/keystore.keystore
- name: Install dependencies
run: yarn install --immutable
- run: yarn build:mobile
- name: Ruby Setup for Fastlane
uses: ruby/setup-ruby@v1
with:
working-directory: 'packages/mobile'
- name: fastlane
uses: maierj/[email protected]
env:
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
BUILD_NUMBER: ${{ github.run_number }}
ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_KEYSTORE_ALIAS }}
ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
GOOGLE_PLAY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY_DATA }}
with:
lane: 'android closed_beta'
subdirectory: 'packages/mobile'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/api
## [1.26.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.97",
"version": "1.26.98",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+24
View File
@@ -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.1.592](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.591](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.590](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.589](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.588](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.587](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.586](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.1.586",
"version": "1.1.592",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
+1 -1
View File
@@ -5,7 +5,7 @@ module.exports = {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
ignorePatterns: ['**/*.spec.ts', '@types', 'node_modules', 'dist'],
ignorePatterns: ['**/*.spec.ts', '@types', 'node_modules', 'dist', 'electron-builder.unsigned.cjs'],
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'@typescript-eslint/no-var-requires': 'off',
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.110.197](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.196](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.195](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.194](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.193](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.192](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)
**Note:** Version bump only for package @standardnotes/desktop
## [3.110.191](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)
**Note:** Version bump only for package @standardnotes/desktop
@@ -386,8 +386,31 @@ async function installComponent(
}
}
function validatePackageIdentifier(identifier: string) {
if (!identifier) {
throw new Error('Package identifier must not be empty')
}
if (identifier.includes('/') || identifier.includes('\\') || identifier === '.' || identifier === '..') {
throw new Error(`Invalid package identifier: ${identifier}`)
}
}
function assertPathWithinExtensions(absolutePath: string) {
const extensionsRoot = path.resolve(Paths.userDataDir, Paths.extensionsDirRelative)
const resolvedPath = path.resolve(absolutePath)
const relativeToExtensions = path.relative(extensionsRoot, resolvedPath)
if (relativeToExtensions.startsWith('..') || path.isAbsolute(relativeToExtensions)) {
throw new Error(`Path escapes extensions directory: ${absolutePath}`)
}
}
function pathsForComponent(component: Pick<Component, 'content'>) {
const relativePath = path.join(Paths.extensionsDirRelative, component.content!.package_info.identifier)
const identifier = component.content!.package_info.identifier
validatePackageIdentifier(identifier)
const relativePath = path.join(Paths.extensionsDirRelative, identifier)
const absolutePath = path.join(Paths.userDataDir, relativePath)
const downloadPath = path.join(Paths.tempDir, AppName, 'downloads', component.content!.name + '.zip')
@@ -404,7 +427,9 @@ async function uninstallComponent(mapping: MappingFileHandler, uuid: string) {
/** No mapping for component */
return
}
const result = await new FilesManager().deleteDir(path.join(Paths.userDataDir, componentMapping.location))
const absolutePath = path.join(Paths.userDataDir, componentMapping.location)
assertPathWithinExtensions(absolutePath)
const result = await new FilesManager().deleteDir(absolutePath)
if (!result.isFailed()) {
mapping.remove(uuid)
}
@@ -0,0 +1,13 @@
const packageJson = require('./package.json')
module.exports = {
...packageJson.build,
afterSign: null,
win: {
...packageJson.build.win,
certificateSubjectName: null,
publisherName: null,
sign: null,
signDlls: false,
},
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.110.191",
"version": "3.110.197",
"license": "AGPL-3.0",
"author": "Standard Notes.",
"private": true,
@@ -143,6 +143,71 @@ test("doesn't download anything when two install/uninstall tasks are queued", as
t.is(downloadFileCallCount, 1)
})
test('does not uninstall paths outside extensions from poisoned mapping', async (t) => {
await packageManager.syncComponents([fakeComponent()])
await new Promise((resolve) => setTimeout(resolve, 200))
const escapeDir = path.join(tmpDir.path, 'escape')
const markerPath = path.join(escapeDir, 'marker.txt')
await ensureDirectoryExists(escapeDir)
await fs.writeFile(markerPath, 'keep')
const poisonedLocations = [path.join('Extensions', '..', 'escape'), path.join('..', 'escape')]
for (const location of poisonedLocations) {
await fs.writeFile(
path.join(contentDir, 'mapping.json'),
JSON.stringify({
[uuid]: { location, version },
}),
)
await packageManager.syncComponents([fakeComponent({ deleted: true })])
await new Promise((resolve) => setTimeout(resolve, 200))
t.true(await fs.stat(markerPath).then(() => true), `marker preserved for location ${location}`)
t.deepEqual(await readJSONFile(path.join(contentDir, 'mapping.json')), {
[uuid]: { location, version },
})
}
t.true(await fs.stat(path.join(contentDir, identifier)).then(() => true))
})
test('rejects path traversal in package identifier', async (t) => {
const traversalIdentifiers = ['../escape', 'foo/../../escape', '..', '.', 'foo/bar']
const extensionsParent = path.dirname(contentDir)
for (const badIdentifier of traversalIdentifiers) {
const before = await fs.readdir(contentDir)
const parentBefore = await fs.readdir(extensionsParent)
downloadFileCallCount = 0
await packageManager.syncComponents([
{
...fakeComponent({ modifier: badIdentifier }),
content: {
...fakeComponent().content,
name: `Bad ${badIdentifier}`,
package_info: {
...fakeComponent().content.package_info,
identifier: badIdentifier,
},
},
},
])
await new Promise((resolve) => setTimeout(resolve, 200))
t.is(downloadFileCallCount, 0, `should not download for identifier ${badIdentifier}`)
t.deepEqual(await fs.readdir(contentDir), before, `extensions dir unchanged for ${badIdentifier}`)
t.deepEqual(
await fs.readdir(extensionsParent),
parentBefore,
`no directory escape for ${badIdentifier}`,
)
}
})
test("Relies on download_url's version field to store the version number", async (t) => {
await packageManager.syncComponents([fakeComponent()])
await new Promise((resolve) => setTimeout(resolve, 200))
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.114](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.113](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.113",
"version": "1.21.114",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.135](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.134](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.134",
"version": "1.28.135",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/files
## [1.20.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.20.17",
"version": "1.20.18",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.58.259](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.258](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.257](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.256](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.255](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.254](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)
**Note:** Version bump only for package @standardnotes/mobile
## [3.58.253](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -73,7 +73,7 @@ def enableProguardInReleaseBuilds = false
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
def appVersionCode = project.hasProperty('versionCode') ? Integer.valueOf(project.property('versionCode')) : 1
def appVersionName = project.hasProperty('versionName') ? project.property('versionName') : "1.0"
def appVersionName = project.hasProperty('versionName') ? project.property('versionName') : "1.0.0"
android {
ndkVersion rootProject.ext.ndkVersion
+6
View File
@@ -161,4 +161,10 @@ platform :android do
version = 3_004_000 + ENV['BUILD_NUMBER'].to_i
deploy_android 'prod', version
end
desc 'Deploy production app to Play Store closed testing (alpha track)'
lane :closed_beta do
version = 3_004_000 + ENV['BUILD_NUMBER'].to_i
deploy_android 'prod', version, 'alpha'
end
end
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.58.253",
"version": "3.58.259",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.58.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
### Bug Fixes
* Fix keyboard shortcut handling from within iframe editors ([#3025](https://github.com/standardnotes/app/issues/3025)) ([ee56c85](https://github.com/standardnotes/app/commit/ee56c858ab2c0d7d5a158e29c070b4bf55c1bd8b))
## [1.58.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.58.3",
"version": "1.58.4",
"publishConfig": {
"access": "public"
},
@@ -27,4 +27,10 @@ export type MessageData = Partial<{
content_type?: string
/** Related to key-pressed action */
keyboardModifier?: KeyboardModifier
key?: string
code?: string
ctrlKey?: boolean
metaKey?: boolean
shiftKey?: boolean
altKey?: boolean
}>
+24
View File
@@ -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.4.899](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.898](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.897](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.896](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.895](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.894](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.893](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.893",
"version": "1.4.899",
"repository": {
"type": "git",
"url": "https://github.com/standardnotes/app",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.72.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/services
## [1.72.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.72.3",
"version": "1.72.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.211.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
**Note:** Version bump only for package @standardnotes/snjs
## [2.211.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/snjs
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.211.7",
"version": "2.211.8",
"repository": {
"type": "git",
"url": "https://github.com/standardnotes/app",
+24
View File
@@ -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.39.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
### Bug Fixes
* Fix keyboard shortcut handling from within iframe editors ([#3025](https://github.com/standardnotes/app/issues/3025)) ([ee56c85](https://github.com/standardnotes/app/commit/ee56c858ab2c0d7d5a158e29c070b4bf55c1bd8b))
## [1.39.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
### Bug Fixes
* Allows demo login on demo host only ([#3021](https://github.com/standardnotes/app/issues/3021)) ([8a20206](https://github.com/standardnotes/app/commit/8a20206cf3957d6f89a89e84b5d5eebe95dd2539))
## [1.39.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.39.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
### Bug Fixes
* Fixes checkboxes parsed as bullet items when importing Evernote notes ([f488518](https://github.com/standardnotes/app/commit/f4885188ad7a289b16f95eeea56de2bed56dfb95))
* Fixes empty lines doubled when importing Evernote notes ([b4faa20](https://github.com/standardnotes/app/commit/b4faa20ac1089187d2655dc132f8252893a51a9f))
* Fixes highlight text style lost when importing Evernote notes ([a62f496](https://github.com/standardnotes/app/commit/a62f496ee6ca588876b616261c479f6499b4ec19))
## [1.39.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-24)
**Note:** Version bump only for package @standardnotes/ui-services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.39.4",
"version": "1.39.8",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -5,7 +5,7 @@
import { ContentType } from '@standardnotes/domain-core'
import { SNNote, SNTag } from '@standardnotes/models'
import { EvernoteConverter, EvernoteResource } from './EvernoteConverter'
import { createTestResourceElement, enex } from './testData'
import { checkboxEnex, createTestResourceElement, emptyLineEnex, enTodoEnex, enex, highlightEnex } from './testData'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { GenerateUuid } from '@standardnotes/services'
import { Converter } from '../Converter'
@@ -109,7 +109,43 @@ describe('EvernoteConverter', () => {
)
})
it('should convert Evernote checkbox lists to super format', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(checkboxEnex as unknown as File, {
...dependencies,
canUseSuper: true,
})
expect((successful?.[0] as SNNote).content.text).toContain('__lexicallisttype="check"')
expect((successful?.[0] as SNNote).content.text).toContain('aria-checked="true"')
expect((successful?.[0] as SNNote).content.text).toContain('aria-checked="false"')
})
it('should convert Evernote checkbox lists to plaintext checkboxes without super', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(checkboxEnex as unknown as File, dependencies)
expect((successful?.[0] as SNNote).content.text).toBe('- [x] Line 1\n- [ ] Line 2\n')
})
it('should convert en-todo tags to super checklist format', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(enTodoEnex as unknown as File, {
...dependencies,
canUseSuper: true,
})
expect((successful?.[0] as SNNote).content.text).toContain('__lexicallisttype="check"')
expect((successful?.[0] as SNNote).content.text).toContain('Checked item')
expect((successful?.[0] as SNNote).content.text).toContain('Unchecked item')
})
it('should convert lists to super format if applicable', () => {
const converter = new EvernoteConverter(generateUuid)
const noteElement = document.createElement('en-note')
const unorderedList1 = document.createElement('ul')
unorderedList1.style.setProperty('--en-todo', 'true')
const listItem1 = document.createElement('li')
@@ -120,11 +156,10 @@ describe('EvernoteConverter', () => {
unorderedList1.appendChild(listItem2)
const unorderedList2 = document.createElement('ul')
noteElement.appendChild(unorderedList1)
noteElement.appendChild(unorderedList2)
const array = [unorderedList1, unorderedList2]
const converter = new EvernoteConverter(generateUuid)
converter.convertListsToSuperFormatIfApplicable(array)
converter.convertEvernoteChecklists(noteElement, true)
expect(unorderedList1.getAttribute('__lexicallisttype')).toBe('check')
expect(listItem1.getAttribute('aria-checked')).toBe('true')
@@ -132,6 +167,49 @@ describe('EvernoteConverter', () => {
expect(unorderedList2.getAttribute('__lexicallisttype')).toBeFalsy()
})
it('should preserve single empty lines from Evernote br-only divs', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(emptyLineEnex as unknown as File, dependencies)
expect((successful?.[0] as SNNote).content.text).toBe('line1\n\nline2')
})
it('should convert highlight spans to mark elements', () => {
const converter = new EvernoteConverter(generateUuid)
const root = document.createElement('div')
root.innerHTML =
'<span style="--en-highlight:yellow;background-color: #ffef9e;">Line 2</span><span>plain</span>'
converter.convertHighlightSpansToMarks(root)
expect(root.querySelector('span')?.textContent).toBe('plain')
expect(root.querySelector('mark')?.textContent).toBe('Line 2')
})
it('should convert highlight spans to mark elements before Super import', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(highlightEnex as unknown as File, {
...dependencies,
canUseSuper: true,
})
expect((successful?.[0] as SNNote).content.text).toContain('<mark')
expect((successful?.[0] as SNNote).content.text).not.toMatch(/<span[^>]*--en-highlight/)
})
it('should convert Evernote br-only divs to empty paragraphs for Super', async () => {
const converter = new EvernoteConverter(generateUuid)
const { successful } = await converter.convert(emptyLineEnex as unknown as File, {
...dependencies,
canUseSuper: true,
})
expect((successful?.[0] as SNNote).content.text).toBe('<p>line1</p><p></p><p>line2</p>')
})
it('should replace media elements with resources', async () => {
const resources: EvernoteResource[] = [
{
@@ -8,6 +8,10 @@ import Base64 from 'crypto-js/enc-base64'
import { Converter, UploadFileFn } from '../Converter'
import { ConversionResult } from '../ConversionResult'
import { getBlobFromBase64 } from '../Utils'
import { isHighlightSpanElement } from '../HighlightSpanImport'
const EVERNOTE_TODO = /--en-todo\s*:\s*true/i
const EVERNOTE_CHECKED = /--en-checked\s*:\s*true/i
dayjs.extend(customParseFormat)
dayjs.extend(utc)
@@ -86,14 +90,13 @@ export class EvernoteConverter implements Converter {
const noteElement = content.getElementsByTagName('en-note')[0] as HTMLElement
const unorderedLists = Array.from(noteElement.getElementsByTagName('ul'))
if (canUseSuper) {
this.convertTopLevelDivsToParagraphs(noteElement)
this.convertListsToSuperFormatIfApplicable(unorderedLists)
this.convertLeftPaddingToSuperIndent(noteElement)
this.convertHighlightSpansToMarks(noteElement)
}
this.convertEvernoteChecklists(noteElement, canUseSuper)
this.removeEmptyAndOrphanListElements(noteElement)
this.unwrapTopLevelBreaks(noteElement)
@@ -242,6 +245,38 @@ export class EvernoteConverter implements Converter {
} as EvernoteResource
}
convertHighlightSpansToMarks(noteElement: HTMLElement) {
for (const span of Array.from(noteElement.querySelectorAll('span'))) {
if (!isHighlightSpanElement(span)) {
continue
}
const mark = document.createElement('mark')
const style = span.getAttribute('style')
if (style) {
mark.setAttribute('style', style)
}
while (span.firstChild) {
mark.appendChild(span.firstChild)
}
span.replaceWith(mark)
}
}
convertEvernoteChecklists(noteElement: HTMLElement, forSuper: boolean) {
for (const ul of Array.from(noteElement.getElementsByTagName('ul'))) {
if (isEvernoteTodoList(ul)) {
convertEvernoteTodoList(ul, forSuper)
}
}
for (const group of getEnTodoBlockGroups(noteElement)) {
convertEvernoteEnTodoGroup(group, forSuper)
}
}
convertTopLevelDivsToParagraphs(noteElement: HTMLElement) {
noteElement.querySelectorAll('div').forEach((div) => {
if (div.parentElement === noteElement) {
@@ -250,21 +285,6 @@ export class EvernoteConverter implements Converter {
})
}
convertListsToSuperFormatIfApplicable(unorderedLists: HTMLUListElement[]) {
for (const unorderedList of unorderedLists) {
if (unorderedList.style.getPropertyValue('--en-todo') !== 'true') {
continue
}
unorderedList.setAttribute('__lexicallisttype', 'check')
const listItems = unorderedList.getElementsByTagName('li')
for (const listItem of Array.from(listItems)) {
listItem.setAttribute('aria-checked', listItem.style.getPropertyValue('--en-checked'))
}
}
}
convertLeftPaddingToSuperIndent(noteElement: HTMLElement) {
noteElement.querySelectorAll('p').forEach((element) => {
const paddingLeft = element.style.paddingLeft
@@ -298,7 +318,7 @@ export class EvernoteConverter implements Converter {
const children = Array.from(parent.children)
const isEveryChildBR = children.every((child) => child.tagName === 'BR')
if (isEveryChildBR) {
parent.replaceWith(children[0])
parent.replaceChildren()
}
})
}
@@ -408,3 +428,135 @@ function changeElementTag(element: HTMLElement, newTag: string) {
}
parent.replaceChild(replacement, element)
}
function isEvernoteStyleTrue(element: HTMLElement, property: '--en-todo' | '--en-checked'): boolean {
const style = element.getAttribute('style') ?? ''
const matchesStyleAttribute = property === '--en-todo' ? EVERNOTE_TODO.test(style) : EVERNOTE_CHECKED.test(style)
return matchesStyleAttribute || element.style.getPropertyValue(property) === 'true'
}
function isEvernoteTodoList(element: HTMLUListElement): boolean {
return isEvernoteStyleTrue(element, '--en-todo')
}
function isEvernoteChecked(element: HTMLElement): boolean {
return isEvernoteStyleTrue(element, '--en-checked')
}
function formatPlaintextCheckbox(checked: boolean, text: string): string {
return `- ${checked ? '[x]' : '[ ]'} ${text}`
}
function moveEnTodoBlockContent(block: HTMLElement, target: HTMLElement) {
const clone = block.cloneNode(true) as HTMLElement
const enTodo = clone.querySelector('en-todo')
if (enTodo) {
while (enTodo.firstChild) {
target.appendChild(enTodo.firstChild)
}
enTodo.remove()
}
while (clone.lastChild?.nodeName === 'BR') {
clone.removeChild(clone.lastChild)
}
while (clone.firstChild) {
target.appendChild(clone.firstChild)
}
}
function getEnTodoBlockGroups(noteElement: HTMLElement): HTMLElement[][] {
const groups: HTMLElement[][] = []
let currentGroup: HTMLElement[] = []
for (const child of Array.from(noteElement.children)) {
if (!(child instanceof HTMLElement) || (child.tagName !== 'DIV' && child.tagName !== 'P')) {
if (currentGroup.length > 0) {
groups.push(currentGroup)
currentGroup = []
}
continue
}
if (child.querySelector('en-todo')) {
currentGroup.push(child)
} else if (currentGroup.length > 0) {
groups.push(currentGroup)
currentGroup = []
}
}
if (currentGroup.length > 0) {
groups.push(currentGroup)
}
return groups
}
function replaceBlockGroup(group: HTMLElement[], replacement: HTMLElement) {
group[0].replaceWith(replacement)
for (let index = 1; index < group.length; index++) {
group[index].remove()
}
}
function convertEvernoteTodoList(ul: HTMLUListElement, forSuper: boolean) {
if (forSuper) {
ul.setAttribute('__lexicallisttype', 'check')
for (const listItem of Array.from(ul.getElementsByTagName('li'))) {
listItem.setAttribute('aria-checked', isEvernoteChecked(listItem) ? 'true' : 'false')
}
return
}
const lines = Array.from(ul.getElementsByTagName('li')).map((listItem) =>
formatPlaintextCheckbox(isEvernoteChecked(listItem), listItem.textContent?.trim() ?? ''),
)
const replacement = document.createElement('div')
replacement.textContent = `${lines.join('\n')}\n`
ul.replaceWith(replacement)
}
function convertEvernoteEnTodoGroup(group: HTMLElement[], forSuper: boolean) {
if (forSuper) {
const ul = document.createElement('ul')
ul.setAttribute('__lexicallisttype', 'check')
for (const block of group) {
const enTodo = block.querySelector('en-todo')
if (!enTodo) {
continue
}
const listItem = document.createElement('li')
const checked = enTodo.getAttribute('checked')?.toLowerCase() === 'true'
listItem.setAttribute('aria-checked', checked ? 'true' : 'false')
moveEnTodoBlockContent(block, listItem)
ul.appendChild(listItem)
}
replaceBlockGroup(group, ul)
return
}
const lines: string[] = []
for (const block of group) {
const enTodo = block.querySelector('en-todo')
if (!enTodo) {
continue
}
const textContainer = document.createElement('div')
moveEnTodoBlockContent(block, textContainer)
const checked = enTodo.getAttribute('checked')?.toLowerCase() === 'true'
lines.push(formatPlaintextCheckbox(checked, textContainer.textContent?.trim() ?? ''))
}
const replacement = document.createElement('div')
replacement.textContent = `${lines.join('\n')}\n`
replaceBlockGroup(group, replacement)
}
@@ -36,6 +36,61 @@ export const enex = `<?xml version="1.0" encoding="UTF-8"?>
</note>
</en-export>`
export const highlightEnex = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export3.dtd">
<en-export export-date="20210408T052957Z" application="Evernote" version="10.8.5">
<note>
<title>Highlight test</title>
<created>20210308T051614Z</created>
<updated>20210308T051855Z</updated>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>Line 1</div><div><span style="--en-highlight:yellow;background-color: #ffef9e;">Line 2</span></div></en-note>]]>
</content>
</note>
</en-export>`
export const checkboxEnex = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20221222T043818Z" application="Evernote" version="10.49.4">
<note>
<title>Checkbox test</title>
<created>20221122T043758Z</created>
<updated>20221122T043813Z</updated>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><ul style="--en-todo:true;"><li style="--en-checked:true;"><div>Line 1</div></li><li><div>Line 2</div></li></ul></en-note>]]>
</content>
</note>
</en-export>`
export const enTodoEnex = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export2.dtd">
<en-export export-date="20200622T091735Z" application="Evernote/Windows" version="6.x">
<note>
<title>En-todo test</title>
<content><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
<en-note><div><en-todo checked="true"/>Checked item<br/></div><div><en-todo checked="false"/>Unchecked item<br/></div></en-note>]]></content>
<created>20200622T091652Z</created>
<updated>20200622T091707Z</updated>
</note>
</en-export>`
export const emptyLineEnex = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export3.dtd">
<en-export export-date="20210408T052957Z" application="Evernote" version="10.8.5">
<note>
<title>Empty line test</title>
<created>20210308T051614Z</created>
<updated>20210308T051855Z</updated>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>line1</div><div><br/></div><div>line2</div></en-note>]]>
</content>
</note>
</en-export>`
export function createTestResourceElement(
shouldHaveMimeType = true,
shouldHaveSourceUrl = false,
@@ -0,0 +1,26 @@
/**
* @jest-environment jsdom
*/
import { isHighlightSpanElement, isHighlightSpanStyle } from './HighlightSpanImport'
describe('HighlightSpanImport', () => {
it('detects --en-highlight in style attribute', () => {
expect(isHighlightSpanStyle('--en-highlight:yellow;background-color: #ffef9e;')).toBe(true)
})
it('detects -evernote-highlight in style attribute', () => {
expect(isHighlightSpanStyle('background-color: rgb(255, 250, 165);-evernote-highlight:true;')).toBe(true)
})
it('does not treat highlight:false as highlighted', () => {
expect(isHighlightSpanStyle('--en-highlight:false;')).toBe(false)
})
it('detects highlight spans by element style', () => {
const span = document.createElement('span')
span.setAttribute('style', '--en-highlight:yellow;background-color: #ffef9e;')
expect(isHighlightSpanElement(span)).toBe(true)
})
})
@@ -0,0 +1,24 @@
const HIGHLIGHT_SPAN_PROPERTY =
/(?:--en-highlight|-en-highlight|--evernote-highlight|-evernote-highlight)\s*:\s*([^;]+)/i
export function isHighlightSpanStyle(styleAttribute: string | null | undefined): boolean {
if (!styleAttribute) {
return false
}
const match = styleAttribute.match(HIGHLIGHT_SPAN_PROPERTY)
if (!match) {
return false
}
return match[1].trim().toLowerCase() !== 'false'
}
export function isHighlightSpanElement(element: HTMLElement): boolean {
if (isHighlightSpanStyle(element.getAttribute('style'))) {
return true
}
const enHighlight = element.style.getPropertyValue('--en-highlight')
return enHighlight !== '' && enHighlight.toLowerCase() !== 'false'
}
@@ -0,0 +1,11 @@
export type ComponentKeyboardEventInit = Pick<
KeyboardEvent,
'key' | 'code' | 'ctrlKey' | 'metaKey' | 'shiftKey' | 'altKey'
>
export function isComponentKeyboardEventInit(data: {
key?: string
code?: string
}): data is ComponentKeyboardEventInit {
return data.key !== undefined && data.code !== undefined
}
@@ -1,4 +1,5 @@
import { Environment, Platform, UuidGenerator } from '@standardnotes/snjs'
import { ComponentKeyboardEventInit } from './ComponentKeyboardEventInit'
import { eventMatchesKeyAndModifiers } from './eventMatchesKeyAndModifiers'
import { KeyboardCommand } from './KeyboardCommands'
import { KeyboardKeyEvent } from './KeyboardKeyEvent'
@@ -101,6 +102,12 @@ export class KeyboardService {
this.removeActiveModifier(modifier)
}
public handleComponentKeyboardEvent = (eventInit: ComponentKeyboardEventInit, keyEvent: KeyboardKeyEvent): void => {
const event = new KeyboardEvent(keyEvent === KeyboardKeyEvent.Down ? 'keydown' : 'keyup', eventInit)
this.updateAllModifiersFromEvent(event)
this.handleKeyboardEvent(event, keyEvent)
}
private handleKeyDown = (event: KeyboardEvent): void => {
this.updateAllModifiersFromEvent(event)
@@ -20,6 +20,13 @@ describe('route parser', () => {
expect(parser.demoParams.token).toEqual('eyJhY2Nlc3NUb2tl')
})
it('ignores demo-token on non-demo hosts', () => {
const url = 'https://app.standardnotes.com/?demo-token=eyJhY2Nlc3NUb2tl'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.None)
})
it('routes to settings', () => {
const url = 'https://app.standardnotes.com/?settings=account'
const parser = new RouteParser(url)
+11 -1
View File
@@ -97,6 +97,10 @@ export class RouteParser implements RouteParserInterface {
}
}
private get isDemoHost(): boolean {
return this.url.host === 'app-demo.standardnotes.com'
}
private parseTypeFromQueryParameters(): RouteType {
if (this.path === RootRoutes.Onboarding) {
return RouteType.Onboarding
@@ -119,7 +123,13 @@ export class RouteParser implements RouteParserInterface {
for (const rootQueryParam of rootQueryParametersMap.keys()) {
if (this.searchParams.has(rootQueryParam)) {
return rootQueryParametersMap.get(rootQueryParam) as RouteType
const routeType = rootQueryParametersMap.get(rootQueryParam) as RouteType
if (routeType === RouteType.Demo && !this.isDemoHost) {
return RouteType.None
}
return routeType
}
}
@@ -0,0 +1,10 @@
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
import { Platform } from '@standardnotes/models'
export class IsAndroid implements SyncUseCaseInterface<boolean> {
constructor(private platform: Platform) {}
execute(): Result<boolean> {
return Result.ok(this.platform === Platform.Android)
}
}
@@ -26,6 +26,7 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleReceivedLinkEvent(item: { link: string; title: string }): Promise<void>
handleOpenFilePreviewEvent(item: { id: string }): void
isNativeMobileWeb(): boolean
canShowPurchaseFlow(): boolean
handleAndroidBackButtonPressed(): void
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined
setAndroidBackHandlerFallbackListener(listener: () => boolean): void
+3
View File
@@ -7,6 +7,8 @@ export * from './Changelog/ChangelogServiceInterface'
export * from './Keyboard/KeyboardService'
export * from './Keyboard/KeyboardShortcut'
export * from './Keyboard/KeyboardCommands'
export * from './Keyboard/KeyboardKeyEvent'
export * from './Keyboard/ComponentKeyboardEventInit'
export * from './Keyboard/platformCheck'
export * from './Keyboard/KeyboardKey'
export * from './Keyboard/KeyboardModifier'
@@ -37,6 +39,7 @@ export * from './UseCase/IsGlobalSpellcheckEnabled'
export * from './UseCase/IsNativeMobileWeb'
export * from './UseCase/IsMobileDevice'
export * from './UseCase/IsNativeIOS'
export * from './UseCase/IsAndroid'
export * from './UseCase/GetItemTags'
export * from './Theme/ThemeManager'
+42
View File
@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.201.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)
### Bug Fixes
* Fix keyboard shortcut handling from within iframe editors ([#3025](https://github.com/standardnotes/app/issues/3025)) ([ee56c85](https://github.com/standardnotes/app/commit/ee56c858ab2c0d7d5a158e29c070b4bf55c1bd8b))
* Fixes gaps after linked items within bullet lists on Super PDF export ([#3023](https://github.com/standardnotes/app/issues/3023)) ([883cd4f](https://github.com/standardnotes/app/commit/883cd4fda968c01648b8465f5cf02cd72d16f514))
* Fixes incorrect rendering of some special characters on Super PDF export ([#3024](https://github.com/standardnotes/app/issues/3024)) ([44a1a6c](https://github.com/standardnotes/app/commit/44a1a6c374ea1d5ee05cc938dce77afe369a1ce5))
* Fixes text alignment in Super linked items ([#3027](https://github.com/standardnotes/app/issues/3027)) ([eec6bc2](https://github.com/standardnotes/app/commit/eec6bc2782fd3b499961f73c33b2b284a69a49b5))
## [3.201.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)
### Bug Fixes
* Fixes default language for newly created code blocks in Super notes ([#2999](https://github.com/standardnotes/app/issues/2999)) ([9d026df](https://github.com/standardnotes/app/commit/9d026df49aedc49b143e7cf91576576c73e49986))
* Fixes Super replace functionality not correctly hidden when toggling Prevent editing on a note ([#3022](https://github.com/standardnotes/app/issues/3022)) ([386d73f](https://github.com/standardnotes/app/commit/386d73ffb839e7a6b3c4bc7578c674fa6b29f658))
## [3.201.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)
**Note:** Version bump only for package @standardnotes/web
## [3.201.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)
**Note:** Version bump only for package @standardnotes/web
## [3.201.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)
### Bug Fixes
* Fixes checkboxes parsed as bullet items when importing Evernote notes ([f488518](https://github.com/standardnotes/app/commit/f4885188ad7a289b16f95eeea56de2bed56dfb95))
* Fixes highlight text style lost when importing Evernote notes ([a62f496](https://github.com/standardnotes/app/commit/a62f496ee6ca588876b616261c479f6499b4ec19))
## [3.201.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)
### Bug Fixes
* Fixes cell padding and bold font within table cells in Super pdf export ([c81589b](https://github.com/standardnotes/app/commit/c81589b945b04b6f2b6842f193495d562c132bc8))
* Fixes images rendering too small on Super pdf export ([80fa337](https://github.com/standardnotes/app/commit/80fa337afba85bada70b4ddacb6e5ad391978f28))
* Fixes Super pdf export code block formatting issues ([bd3cf60](https://github.com/standardnotes/app/commit/bd3cf600e7a766c2fd980fbc01924e59664184b8))
* Fixes Super pdf export headings font sizes and weights ([6daf58c](https://github.com/standardnotes/app/commit/6daf58c92893a6cfa93596a2429ed3e0697222e0))
* Fixes Super pdf export line height ([e22e6fc](https://github.com/standardnotes/app/commit/e22e6fce7936a9b425f90e7363f274bce3b3b841))
* Fixes Upgrade button in Settings leading to purchase page ([#3011](https://github.com/standardnotes/app/issues/3011)) ([eda1bfe](https://github.com/standardnotes/app/commit/eda1bfe0bd5b2be762dadf840f49034a2c659816))
## [3.201.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)
### Bug Fixes
+98
View File
@@ -1,5 +1,103 @@
{
"versions": [
{
"version": "3.201.33",
"title": "[3.201.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-18)",
"date": null,
"body": "### Bug Fixes\n\n* Fix keyboard shortcut handling from within iframe editors ([#3025](https://github.com/standardnotes/app/issues/3025)) ([ee56c85](https://github.com/standardnotes/app/commit/ee56c858ab2c0d7d5a158e29c070b4bf55c1bd8b))\n* Fixes gaps after linked items within bullet lists on Super PDF export ([#3023](https://github.com/standardnotes/app/issues/3023)) ([883cd4f](https://github.com/standardnotes/app/commit/883cd4fda968c01648b8465f5cf02cd72d16f514))\n* Fixes incorrect rendering of some special characters on Super PDF export ([#3024](https://github.com/standardnotes/app/issues/3024)) ([44a1a6c](https://github.com/standardnotes/app/commit/44a1a6c374ea1d5ee05cc938dce77afe369a1ce5))\n* Fixes text alignment in Super linked items ([#3027](https://github.com/standardnotes/app/issues/3027)) ([eec6bc2](https://github.com/standardnotes/app/commit/eec6bc2782fd3b499961f73c33b2b284a69a49b5))",
"parsed": {
"_": [
"Fix keyboard shortcut handling from within iframe editors (#3025) (ee56c85)",
"Fixes gaps after linked items within bullet lists on Super PDF export (#3023) (883cd4f)",
"Fixes incorrect rendering of some special characters on Super PDF export (#3024) (44a1a6c)",
"Fixes text alignment in Super linked items (#3027) (eec6bc2)"
],
"Bug Fixes": [
"Fix keyboard shortcut handling from within iframe editors (#3025) (ee56c85)",
"Fixes gaps after linked items within bullet lists on Super PDF export (#3023) (883cd4f)",
"Fixes incorrect rendering of some special characters on Super PDF export (#3024) (44a1a6c)",
"Fixes text alignment in Super linked items (#3027) (eec6bc2)"
]
}
},
{
"version": "3.201.32",
"title": "[3.201.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-07-06)",
"date": null,
"body": "### Bug Fixes\n\n* Fixes default language for newly created code blocks in Super notes ([#2999](https://github.com/standardnotes/app/issues/2999)) ([9d026df](https://github.com/standardnotes/app/commit/9d026df49aedc49b143e7cf91576576c73e49986))\n* Fixes Super replace functionality not correctly hidden when toggling Prevent editing on a note ([#3022](https://github.com/standardnotes/app/issues/3022)) ([386d73f](https://github.com/standardnotes/app/commit/386d73ffb839e7a6b3c4bc7578c674fa6b29f658))",
"parsed": {
"_": [
"Fixes default language for newly created code blocks in Super notes (#2999) (9d026df)",
"Fixes Super replace functionality not correctly hidden when toggling Prevent editing on a note (#3022) (386d73f)"
],
"Bug Fixes": [
"Fixes default language for newly created code blocks in Super notes (#2999) (9d026df)",
"Fixes Super replace functionality not correctly hidden when toggling Prevent editing on a note (#3022) (386d73f)"
]
}
},
{
"version": "3.201.31",
"title": "[3.201.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-08)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.201.30",
"title": "[3.201.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-07)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.201.29",
"title": "[3.201.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-06-05)",
"date": null,
"body": "### Bug Fixes\n\n* Fixes checkboxes parsed as bullet items when importing Evernote notes ([f488518](https://github.com/standardnotes/app/commit/f4885188ad7a289b16f95eeea56de2bed56dfb95))\n* Fixes highlight text style lost when importing Evernote notes ([a62f496](https://github.com/standardnotes/app/commit/a62f496ee6ca588876b616261c479f6499b4ec19))",
"parsed": {
"_": [
"Fixes checkboxes parsed as bullet items when importing Evernote notes (f488518)",
"Fixes highlight text style lost when importing Evernote notes (a62f496)"
],
"Bug Fixes": [
"Fixes checkboxes parsed as bullet items when importing Evernote notes (f488518)",
"Fixes highlight text style lost when importing Evernote notes (a62f496)"
]
}
},
{
"version": "3.201.28",
"title": "[3.201.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-05-27)",
"date": null,
"body": "### Bug Fixes\n\n* Fixes cell padding and bold font within table cells in Super pdf export ([c81589b](https://github.com/standardnotes/app/commit/c81589b945b04b6f2b6842f193495d562c132bc8))\n* Fixes images rendering too small on Super pdf export ([80fa337](https://github.com/standardnotes/app/commit/80fa337afba85bada70b4ddacb6e5ad391978f28))\n* Fixes Super pdf export code block formatting issues ([bd3cf60](https://github.com/standardnotes/app/commit/bd3cf600e7a766c2fd980fbc01924e59664184b8))\n* Fixes Super pdf export headings font sizes and weights ([6daf58c](https://github.com/standardnotes/app/commit/6daf58c92893a6cfa93596a2429ed3e0697222e0))\n* Fixes Super pdf export line height ([e22e6fc](https://github.com/standardnotes/app/commit/e22e6fce7936a9b425f90e7363f274bce3b3b841))\n* Fixes Upgrade button in Settings leading to purchase page ([#3011](https://github.com/standardnotes/app/issues/3011)) ([eda1bfe](https://github.com/standardnotes/app/commit/eda1bfe0bd5b2be762dadf840f49034a2c659816))",
"parsed": {
"_": [
"Fixes cell padding and bold font within table cells in Super pdf export (c81589b)",
"Fixes images rendering too small on Super pdf export (80fa337)",
"Fixes Super pdf export code block formatting issues (bd3cf60)",
"Fixes Super pdf export headings font sizes and weights (6daf58c)",
"Fixes Super pdf export line height (e22e6fc)",
"Fixes Upgrade button in Settings leading to purchase page (#3011) (eda1bfe)"
],
"Bug Fixes": [
"Fixes cell padding and bold font within table cells in Super pdf export (c81589b)",
"Fixes images rendering too small on Super pdf export (80fa337)",
"Fixes Super pdf export code block formatting issues (bd3cf60)",
"Fixes Super pdf export headings font sizes and weights (6daf58c)",
"Fixes Super pdf export line height (e22e6fc)",
"Fixes Upgrade button in Settings leading to purchase page (#3011) (eda1bfe)"
]
}
},
{
"version": "3.201.27",
"title": "[3.201.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2026-04-29)",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.201.27",
"version": "3.201.33",
"license": "AGPL-3.0",
"main": "dist/app.js",
"author": "Standard Notes",
@@ -117,7 +117,7 @@
"@lexical/rich-text": "0.43.0",
"@lexical/utils": "0.43.0",
"@radix-ui/react-slot": "^1.0.1",
"@react-pdf/renderer": "^4.3.0",
"@react-pdf/renderer": "^4.4.1",
"comlink": "^4.4.1",
"fast-diff": "^1.3.0",
"lexical": "0.43.0",
@@ -48,6 +48,7 @@ export const Web_TYPES = {
IsGlobalSpellcheckEnabled: Symbol.for('IsGlobalSpellcheckEnabled'),
IsMobileDevice: Symbol.for('IsMobileDevice'),
IsNativeIOS: Symbol.for('IsNativeIOS'),
IsAndroid: Symbol.for('IsAndroid'),
IsNativeMobileWeb: Symbol.for('IsNativeMobileWeb'),
IsTabletOrMobileScreen: Symbol.for('IsTabletOrMobileScreen'),
LoadPurchaseFlowUrl: Symbol.for('LoadPurchaseFlowUrl'),
@@ -7,6 +7,7 @@ import {
IsGlobalSpellcheckEnabled,
IsMobileDevice,
IsNativeIOS,
IsAndroid,
IsNativeMobileWeb,
KeyboardService,
PluginsService,
@@ -77,6 +78,10 @@ export class WebDependencies extends DependencyContainer {
return new IsNativeIOS(application.environment, application.platform)
})
this.bind(Web_TYPES.IsAndroid, () => {
return new IsAndroid(application.platform)
})
this.bind(Web_TYPES.OpenSubscriptionDashboard, () => {
return new OpenSubscriptionDashboard(application, application.legacyApi)
})
@@ -331,6 +336,7 @@ export class WebDependencies extends DependencyContainer {
application.mobileDevice,
this.get<LoadPurchaseFlowUrl>(Web_TYPES.LoadPurchaseFlowUrl),
this.get<IsNativeIOS>(Web_TYPES.IsNativeIOS),
this.get<IsAndroid>(Web_TYPES.IsAndroid),
application.events,
)
})
@@ -37,6 +37,7 @@ import {
IsGlobalSpellcheckEnabled,
IsMobileDevice,
IsNativeIOS,
IsAndroid,
IsNativeMobileWeb,
KeyboardService,
PluginsServiceInterface,
@@ -257,12 +258,20 @@ export class WebApplication extends SNApplication implements WebApplicationInter
return this.deps.get<IsNativeIOS>(Web_TYPES.IsNativeIOS).execute().getValue()
}
isAndroid(): boolean {
return this.deps.get<IsAndroid>(Web_TYPES.IsAndroid).execute().getValue()
}
canShowPurchaseFlow(): boolean {
return !this.isAndroid()
}
get isMobileDevice(): boolean {
return this.deps.get<IsMobileDevice>(Web_TYPES.IsMobileDevice).execute().getValue()
}
get hideOutboundSubscriptionLinks() {
return this.isNativeIOS()
return this.isNativeIOS() || this.isAndroid()
}
get mobileDevice(): MobileDeviceInterface {
@@ -6,6 +6,7 @@ import Icon from '@/Components/Icon/Icon'
import { useApplication } from '../ApplicationProvider'
import ServerPicker from './ServerPicker/ServerPicker'
import { DefaultHost } from '@standardnotes/snjs'
import { c } from 'ttag'
type Props = {
disabled?: boolean
@@ -50,7 +51,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
if (!identifier) {
if (privateUsername?.length > 0) {
application.alerts.alert('Unable to compute private username.').catch(console.error)
application.alerts.alert(c('Error').t`Unable to compute private username.`).catch(console.error)
}
return
}
@@ -112,7 +113,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
onClick={toggleShowAdvanced}
>
<div className="flex items-center">
Advanced options
{c('Action').t`Advanced options`}
<Icon type="chevron-down" className="ml-1 text-passive-1" />
</div>
</button>
@@ -124,12 +125,17 @@ const AdvancedOptions: FunctionComponent<Props> = ({
<div className="mb-1 flex items-center justify-between">
<Checkbox
name="private-workspace"
label="Private username mode"
label={c('Option').t`Private username mode`}
checked={isPrivateUsername}
disabled={disabled || isRecoveryCodes}
onChange={handleIsPrivateUsernameChange}
/>
<a href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" title="Learn more">
<a
href="https://standardnotes.com/help/80"
target="_blank"
rel="noopener noreferrer"
title={c('Action').t`Learn more`}
>
<Icon type="info" className="text-neutral" />
</a>
</div>
@@ -140,7 +146,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
className={{ container: 'mb-2' }}
left={[<Icon type="account-circle" className="text-neutral" />]}
type="text"
placeholder="Username"
placeholder={c('Label').t`Username`}
value={privateUsername}
onChange={handlePrivateUsernameNameChange}
disabled={disabled || isRecoveryCodes}
@@ -154,7 +160,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
<div className="mb-1 flex items-center justify-between">
<Checkbox
name="use-strict-signin"
label="Use strict sign-in"
label={c('Option').t`Use strict sign-in`}
checked={isStrictSignin}
disabled={disabled || isRecoveryCodes}
onChange={handleStrictSigninChange}
@@ -163,7 +169,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
href="https://standardnotes.com/help/security"
target="_blank"
rel="noopener noreferrer"
title="Learn more"
title={c('Action').t`Learn more`}
>
<Icon type="info" className="text-neutral" />
</a>
@@ -174,7 +180,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
<div className="mb-1 flex items-center justify-between">
<Checkbox
name="recovery-codes"
label="Use recovery code"
label={c('Option').t`Use recovery code`}
checked={isRecoveryCodes}
disabled={disabled}
onChange={handleIsRecoveryCodesChange}
@@ -188,7 +194,7 @@ const AdvancedOptions: FunctionComponent<Props> = ({
className={{ container: 'mb-2' }}
left={[<Icon type="security" className="text-neutral" />]}
type="text"
placeholder="Recovery code"
placeholder={c('Label').t`Recovery code`}
value={recoveryCodes}
onChange={handleRecoveryCodesChange}
disabled={disabled}
@@ -2,6 +2,7 @@ import AlertDialog from '@/Components/AlertDialog/AlertDialog'
import Button from '@/Components/Button/Button'
import Icon from '@/Components/Icon/Icon'
import { FunctionComponent } from 'react'
import { c } from 'ttag'
type Props = {
onClose: () => void
@@ -12,7 +13,7 @@ const ConfirmNoMergeDialog: FunctionComponent<Props> = ({ onClose, onConfirm })
return (
<AlertDialog closeDialog={onClose}>
<div className="flex items-center justify-between text-lg font-bold">
Delete local data?
{c('Title').t`Delete local data?`}
<button className="rounded p-1 font-bold hover:bg-contrast" onClick={onClose}>
<Icon type="close" />
</button>
@@ -20,18 +21,18 @@ const ConfirmNoMergeDialog: FunctionComponent<Props> = ({ onClose, onConfirm })
<div className="sk-panel-row">
<div>
<p className="text-base text-foreground lg:text-sm">
You have chosen not to merge your local data. If you proceed, your local notes and tags will be permanently
deleted and replaced with data from your account. This action cannot be undone.
{c('Info')
.t`You have chosen not to merge your local data. If you proceed, your local notes and tags will be permanently deleted and replaced with data from your account. This action cannot be undone.`}
</p>
<p className="mt-2 text-base font-semibold text-danger lg:text-sm">
Are you sure you want to continue without merging?
{c('Info').t`Are you sure you want to continue without merging?`}
</p>
</div>
</div>
<div className="mt-4 flex justify-end gap-2">
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onClose}>{c('Action').t`Cancel`}</Button>
<Button primary colorStyle="danger" onClick={onConfirm}>
Delete Local Data and Continue
{c('Action').t`Delete Local Data and Continue`}
</Button>
</div>
</AlertDialog>
@@ -122,7 +122,7 @@ const ConfirmPassword: FunctionComponent<Props> = ({ setMenuPane, email, passwor
}
if (password !== confirmPassword) {
setError(STRING_NON_MATCHING_PASSWORDS)
setError(STRING_NON_MATCHING_PASSWORDS())
setConfirmPassword('')
passwordInputRef.current?.focus()
return
@@ -47,7 +47,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
}
})
.catch(() => {
application.alerts.alert(STRING_GENERIC_SYNC_ERROR).catch(console.error)
application.alerts.alert(STRING_GENERIC_SYNC_ERROR()).catch(console.error)
})
.finally(() => {
setIsSyncingInProgress(false)
@@ -69,9 +69,9 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
}, [application])
const openEmail = useCallback(() => {
const subject = 'Standard Notes Feedback'
const subject = c('MailtoSubject').t`Standard Notes Feedback`
const body = `App Version: ${application.version}`
const body = c('MailtoBody').t`App Version: ${application.version}`
application.device.openUrl(
`mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`,
@@ -103,7 +103,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
return (
<>
<div className="mb-1 mt-1 hidden items-center justify-between px-4 md:flex md:px-3">
<div className="text-lg font-bold lg:text-base">Account</div>
<div className="text-lg font-bold lg:text-base">{c('Title').t`Account`}</div>
<div className="flex cursor-pointer" onClick={closeMenu}>
<Icon type="close" className="text-neutral" />
</div>
@@ -111,7 +111,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
{user ? (
<>
<div className="mb-3 px-4 text-lg text-foreground md:px-3 lg:text-sm">
<div>You're signed in as:</div>
<div>{c('Info').t`You're signed in as:`}</div>
<div className="wrap my-0.5 font-bold">{user.email}</div>
<span className="text-neutral">{application.getHost.execute().getValue()}</span>
</div>
@@ -119,13 +119,13 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
{isSyncingInProgress ? (
<div className="flex items-center font-semibold text-info">
<Spinner className="mr-2 h-5 w-5" />
Syncing...
{c('Status').t`Syncing...`}
</div>
) : (
<div className="flex items-start">
<Icon type="check-circle" className={`mr-2 text-success ${MenuItemIconSize}`} />
<div>
<div className="font-semibold text-success">Last synced:</div>
<div className="font-semibold text-success">{c('Label').t`Last synced:`}</div>
<div className="text-text">{lastSyncDate}</div>
</div>
</div>
@@ -161,7 +161,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
{user ? (
<MenuItem onClick={openPreferences}>
<Icon type="user" className={iconClassName} />
Account settings
{c('Action').t`Account settings`}
</MenuItem>
) : (
<>
@@ -182,18 +182,18 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
}}
>
<Icon type="archive" className={iconClassName} />
Import
{c('Action').t`Import`}
</MenuItem>
{application.isNativeMobileWeb() && (
<MenuItem onClick={openEmail}>
<Icon type="email-filled" className={iconClassName} />
Email us
{c('Action').t`Email us`}
</MenuItem>
)}
<MenuItem className="justify-between" onClick={openHelp}>
<div className="flex items-center">
<Icon type="help" className={iconClassName} />
Help &amp; feedback
{c('Action').t`Help & feedback`}
</div>
<span className="text-neutral">v{application.version}</span>
</MenuItem>
@@ -205,7 +205,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
}}
>
<Icon type="keyboard" className={iconClassName} />
Keyboard shortcuts
{c('Action').t`Keyboard shortcuts`}
{keyboardShortcutsHelpShortcut && (
<KeyboardShortcutIndicator shortcut={keyboardShortcutsHelpShortcut} className="ml-auto" />
)}
@@ -216,7 +216,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
}}
>
<Icon type="info" className={iconClassName} />
Command palette
{c('Action').t`Command palette`}
{commandPaletteShortcut && (
<KeyboardShortcutIndicator shortcut={commandPaletteShortcut} className="ml-auto" />
)}
@@ -228,7 +228,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu,
<MenuSection>
<MenuItem onClick={signOut}>
<Icon type="signOut" className={iconClassName} />
Sign out workspace
{c('Action').t`Sign out workspace`}
</MenuItem>
</MenuSection>
) : null}
@@ -1,6 +1,7 @@
import Icon from '@/Components/Icon/Icon'
import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip'
import { ChangeEventHandler, FunctionComponent } from 'react'
import { c } from 'ttag'
type Props = {
checked: boolean
@@ -21,9 +22,10 @@ const MergeLocalDataCheckbox: FunctionComponent<Props> = ({ checked, onChange, d
onChange={onChange}
disabled={disabled}
/>
<span className="text-danger">Merge local data ({notesAndTagsCount} notes and tags)</span>
<span className="text-danger">{c('Option').t`Merge local data (${notesAndTagsCount} notes and tags)`}</span>
<StyledTooltip
label="If unchecked, your local notes and tags will be permanently deleted and replaced with data from your account."
label={c('Info')
.t`If unchecked, your local notes and tags will be permanently deleted and replaced with data from your account.`}
showOnMobile
className="!z-modal !max-w-[30ch] whitespace-normal"
>
@@ -6,6 +6,7 @@ import { useApplication } from '@/Components/ApplicationProvider'
import { isDesktopApplication } from '@/Utils'
import RadioButtonGroup from '@/Components/RadioButtonGroup/RadioButtonGroup'
import { DefaultHost } from '@standardnotes/snjs'
import { c } from 'ttag'
type Props = {
className?: string
@@ -49,7 +50,7 @@ const ServerPicker = ({ className }: Props) => {
} else if (type === 'home server') {
if (!application.homeServer) {
application.alerts
.alert('Home server is not running. Please open the prefences and home server tab to start it.')
.alert(c('Error').t`Home server is not running. Please open the prefences and home server tab to start it.`)
.catch(console.error)
return
@@ -58,7 +59,7 @@ const ServerPicker = ({ className }: Props) => {
const homeServerUrl = await application.homeServer.getHomeServerUrl()
if (!homeServerUrl) {
application.alerts
.alert('Home server is not running. Please open the prefences and home server tab to start it.')
.alert(c('Error').t`Home server is not running. Please open the prefences and home server tab to start it.`)
.catch(console.error)
return
@@ -71,9 +72,9 @@ const ServerPicker = ({ className }: Props) => {
const options = useMemo(
() =>
[
{ label: 'Default', value: 'standard' },
{ label: 'Custom', value: 'custom' },
].concat(isDesktopApplication() ? [{ label: 'Home Server', value: 'home server' }] : []) as {
{ label: c('Option').t`Default`, value: 'standard' },
{ label: c('Option').t`Custom`, value: 'custom' },
].concat(isDesktopApplication() ? [{ label: c('Option').t`Home Server`, value: 'home server' }] : []) as {
label: string
value: ServerType
}[],
@@ -82,7 +83,7 @@ const ServerPicker = ({ className }: Props) => {
return (
<div className={`flex h-full flex-grow flex-col px-3 pb-1.5 ${className}`}>
<div className="mb-2 flex font-bold">Sync Server</div>
<div className="mb-2 flex font-bold">{c('Label').t`Sync Server`}</div>
<RadioButtonGroup value={currentType} items={options} onChange={selectTab} />
{currentType === 'custom' && (
<DecoratedInput
@@ -1,6 +1,7 @@
import { observer } from 'mobx-react-lite'
import { User as UserType } from '@standardnotes/snjs'
import { useApplication } from '../ApplicationProvider'
import { c } from 'ttag'
const User = () => {
const application = useApplication()
@@ -12,9 +13,10 @@ const User = () => {
<div className="sk-panel-section">
{application.syncStatusController.errorMessage && (
<div className="sk-notification danger">
<div className="sk-notification-title">Sync Unreachable</div>
<div className="sk-notification-title">{c('Title').t`Sync Unreachable`}</div>
<div className="sk-notification-text">
Hmm...we can't seem to sync your account. The reason: {application.syncStatusController.errorMessage}
{c('Error')
.t`Hmm...we can't seem to sync your account. The reason: ${application.syncStatusController.errorMessage}`}
</div>
<a
className="sk-a info-contrast sk-bold sk-panel-row"
@@ -22,7 +24,7 @@ const User = () => {
rel="noopener"
target="_blank"
>
Need help?
{c('Action').t`Need help?`}
</a>
</div>
)}
@@ -8,6 +8,7 @@ import MenuItem from '@/Components/Menu/MenuItem'
import WorkspaceMenuItem from './WorkspaceMenuItem'
import { useApplication } from '@/Components/ApplicationProvider'
import MenuSection from '@/Components/Menu/MenuSection'
import { c } from 'ttag'
type Props = {
mainApplicationGroup: WebApplicationGroup
@@ -42,9 +43,9 @@ const WorkspaceSwitcherMenu: FunctionComponent<Props> = ({
const signoutAll = useCallback(async () => {
const confirmed = await application.alerts.confirm(
'Are you sure you want to sign out of all workspaces on this device?',
c('Info').t`Are you sure you want to sign out of all workspaces on this device?`,
undefined,
'Sign out all',
c('Action').t`Sign out all`,
ButtonType.Danger,
)
if (!confirmed) {
@@ -86,12 +87,12 @@ const WorkspaceSwitcherMenu: FunctionComponent<Props> = ({
<MenuSection>
<MenuItem onClick={addAnotherWorkspace}>
<Icon type="user-add" className="mr-2 text-neutral" />
Add another workspace
{c('Action').t`Add another workspace`}
</MenuItem>
{!hideWorkspaceOptions && (
<MenuItem onClick={signoutAll}>
<Icon type="signOut" className="mr-2 text-neutral" />
Sign out all workspaces
{c('Action').t`Sign out all workspaces`}
</MenuItem>
)}
</MenuSection>
@@ -7,6 +7,7 @@ import WorkspaceSwitcherMenu from './WorkspaceSwitcherMenu'
import MenuItem from '@/Components/Menu/MenuItem'
import Popover from '@/Components/Popover/Popover'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
import { c } from 'ttag'
type Props = {
mainApplicationGroup: WebApplicationGroup
@@ -25,12 +26,12 @@ const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGrou
<MenuItem tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} ref={buttonRef} onClick={toggleMenu} className="justify-between">
<div className="flex items-center">
<Icon type="user-switch" className={`mr-2 text-neutral ${MenuItemIconSize}`} />
Switch workspace
{c('Action').t`Switch workspace`}
</div>
<Icon type="chevron-right" className={`text-neutral ${MenuItemIconSize}`} />
</MenuItem>
<Popover
title="Switch workspace"
title={c('Action').t`Switch workspace`}
align="end"
anchorElement={buttonRef}
className="pb-2"
@@ -7,6 +7,7 @@ import {
ComponentInterface,
SubscriptionManagerEvent,
} from '@standardnotes/snjs'
import { KeyboardKeyEvent, type KeyboardModifier } from '@standardnotes/ui-services'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { observer } from 'mobx-react-lite'
import OfflineRestricted from '@/Components/ComponentView/OfflineRestricted'
@@ -153,12 +154,50 @@ const IframeFeatureView: FunctionComponent<Props> = ({
useEffect(() => {
const removeActionObserver = componentViewer.addActionObserver((action, data) => {
const keyboardData = data as {
key?: string
code?: string
ctrlKey?: boolean
metaKey?: boolean
shiftKey?: boolean
altKey?: boolean
keyboardModifier?: KeyboardModifier
}
switch (action) {
case ComponentAction.KeyDown:
application.keyboardService.handleComponentKeyDown(data.keyboardModifier)
if (keyboardData.key !== undefined && keyboardData.code !== undefined) {
application.keyboardService.handleComponentKeyboardEvent(
{
key: keyboardData.key,
code: keyboardData.code,
ctrlKey: keyboardData.ctrlKey ?? false,
metaKey: keyboardData.metaKey ?? false,
shiftKey: keyboardData.shiftKey ?? false,
altKey: keyboardData.altKey ?? false,
},
KeyboardKeyEvent.Down,
)
} else {
application.keyboardService.handleComponentKeyDown(keyboardData.keyboardModifier)
}
break
case ComponentAction.KeyUp:
application.keyboardService.handleComponentKeyUp(data.keyboardModifier)
if (keyboardData.key !== undefined && keyboardData.code !== undefined) {
application.keyboardService.handleComponentKeyboardEvent(
{
key: keyboardData.key,
code: keyboardData.code,
ctrlKey: keyboardData.ctrlKey ?? false,
metaKey: keyboardData.metaKey ?? false,
shiftKey: keyboardData.shiftKey ?? false,
altKey: keyboardData.altKey ?? false,
},
KeyboardKeyEvent.Up,
)
} else {
application.keyboardService.handleComponentKeyUp(keyboardData.keyboardModifier)
}
break
case ComponentAction.Click:
application.notesController.setContextMenuOpen(false)
@@ -50,9 +50,11 @@ const NotEntitledBanner: FunctionComponent<Props> = ({ featureStatus, feature })
</div>
</div>
<div className={'right'}>
<Button onClick={manageSubscription} primary colorStyle="success" small>
Manage subscription
</Button>
{application.canShowPurchaseFlow() && (
<Button onClick={manageSubscription} primary colorStyle="success" small>
Manage subscription
</Button>
)}
</div>
</div>
</div>
@@ -1,5 +1,6 @@
import { observer } from 'mobx-react-lite'
import { useCallback, useRef } from 'react'
import { c } from 'ttag'
import { STRING_DELETE_ACCOUNT_CONFIRMATION } from '@/Constants/Strings'
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/Application/WebApplication'
@@ -25,22 +26,22 @@ const ConfirmDeleteAccountModal = ({ application }: Props) => {
return (
<AlertDialog closeDialog={closeDialog}>
<div className="flex items-center justify-between text-lg font-bold">
Delete account?
{c('Title').t`Delete account?`}
<button className="rounded p-1 font-bold hover:bg-contrast" onClick={closeDialog}>
<Icon type="close" />
</button>
</div>
<div className="sk-panel-row">
<div>
<p className="text-base text-foreground lg:text-sm">{STRING_DELETE_ACCOUNT_CONFIRMATION}</p>
<p className="text-base text-foreground lg:text-sm">{STRING_DELETE_ACCOUNT_CONFIRMATION()}</p>
</div>
</div>
<div className="mt-4 flex justify-end gap-2">
<Button ref={cancelRef} onClick={closeDialog}>
Cancel
{c('Action').t`Cancel`}
</Button>
<Button primary colorStyle="danger" onClick={confirm}>
Delete my account for good
{c('Action').t`Delete my account for good`}
</Button>
</div>
</AlertDialog>
@@ -8,6 +8,7 @@ import Button from '@/Components/Button/Button'
import Icon from '../Icon/Icon'
import AlertDialog from '../AlertDialog/AlertDialog'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { c } from 'ttag'
type Props = {
application: WebApplication
@@ -37,22 +38,21 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, applicatio
return (
<AlertDialog closeDialog={closeDialog}>
<div className="flex items-center justify-between text-lg font-bold">
Sign out workspace?
{c('Title').t`Sign out workspace?`}
<button className="rounded p-1 font-bold hover:bg-contrast" onClick={closeDialog}>
<Icon type="close" />
</button>
</div>
<div className="sk-panel-row">
<div>
<p className="text-base text-foreground lg:text-sm">{STRING_SIGN_OUT_CONFIRMATION}</p>
<p className="text-base text-foreground lg:text-sm">{STRING_SIGN_OUT_CONFIRMATION()}</p>
{showWorkspaceWarning && (
<>
<br />
<p className="text-base text-foreground lg:text-sm">
<strong>Note: </strong>
Because you have other workspaces signed in, this sign out may leave logs and other metadata of your
session on this device. For a more robust sign out that performs a hard clear of all app-related data,
use the <i>Sign out all workspaces</i> option under <i>Switch workspace</i>.
<strong>{c('Label').t`Note:`} </strong>
{c('Info')
.t`Because you have other workspaces signed in, this sign out may leave logs and other metadata of your session on this device. For a more robust sign out that performs a hard clear of all app-related data, use the "Sign out all workspaces" option under "Switch workspace".`}
</p>
</>
)}
@@ -66,7 +66,8 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, applicatio
<div className="sk-panel-row"></div>
<div>
<p className="text-base text-foreground lg:text-sm">
Local backups are enabled for this workspace. Review your backup files manually to decide what to keep.
{c('Info')
.t`Local backups are enabled for this workspace. Review your backup files manually to decide what to keep.`}
</p>
<button
className="sk-a mt-2 cursor-pointer rounded p-0 capitalize lg:text-sm"
@@ -74,7 +75,7 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, applicatio
void application.fileBackups?.openAllDirectoriesContainingBackupFiles()
}}
>
View backup files
{c('Action').t`View backup files`}
</button>
</div>
</div>
@@ -83,10 +84,10 @@ const ConfirmSignoutModal: FunctionComponent<Props> = ({ application, applicatio
<div className="mt-4 flex justify-end gap-2">
<Button ref={cancelRef} onClick={closeDialog}>
Cancel
{c('Action').t`Cancel`}
</Button>
<Button primary colorStyle="danger" onClick={confirm}>
{application.hasAccount() ? 'Sign Out' : 'Delete Workspace'}
{application.hasAccount() ? c('Action').t`Sign Out` : c('Action').t`Delete Workspace`}
</Button>
</div>
</AlertDialog>
@@ -287,12 +287,12 @@ class Footer extends AbstractComponent<Props, State> {
securityUpdateClickHandler = async () => {
if (
await confirmDialog({
title: STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE,
text: STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT,
confirmButtonText: STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON,
title: STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE(),
text: STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT(),
confirmButtonText: STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON(),
})
) {
preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_UPGRADE, async () => {
preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_UPGRADE(), async () => {
await this.application.upgradeProtocolVersion()
}).catch(console.error)
}

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