mirror of
https://github.com/standardnotes/app
synced 2026-09-20 12:13:48 -04:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac51afa76 | ||
|
|
eec6bc2782 | ||
|
|
ee56c858ab | ||
|
|
44a1a6c374 | ||
|
|
883cd4fda9 | ||
|
|
981a8149f5 | ||
|
|
4e3aa7d69f | ||
|
|
8a20206cf3 | ||
|
|
386d73ffb8 | ||
|
|
9d026df49a | ||
|
|
089bc421fc | ||
|
|
29223e96c0 | ||
|
|
12503f7c5a | ||
|
|
195d1a6233 | ||
|
|
3d8bae26e8 | ||
|
|
fe4d0aacbf | ||
|
|
ed1cd0c366 | ||
|
|
c2bdda2ccb | ||
|
|
09ffb07909 | ||
|
|
81bd2de425 | ||
|
|
f4885188ad | ||
|
|
a62f496ee6 | ||
|
|
b4faa20ac1 | ||
|
|
6c07fab9e9 | ||
|
|
61bf8ca2f6 | ||
|
|
80fa337afb | ||
|
|
cd7046bb69 | ||
|
|
c81589b945 | ||
|
|
bd3cf600e7 | ||
|
|
e22e6fce79 | ||
|
|
6daf58c928 | ||
|
|
eda1bfe0bd | ||
|
|
7ce6e75bc7 | ||
|
|
a5984ae5b4 |
@@ -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 }}
|
||||
@@ -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.
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.
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.97",
|
||||
"version": "1.26.98",
|
||||
"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.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,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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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))
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.113",
|
||||
"version": "1.21.114",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.134",
|
||||
"version": "1.28.135",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.20.17",
|
||||
"version": "1.20.18",
|
||||
"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.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.58.253",
|
||||
"version": "3.58.259",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.58.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,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
|
||||
}>
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.893",
|
||||
"version": "1.4.899",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/standardnotes/app",
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.72.3",
|
||||
"version": "1.72.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.211.7",
|
||||
"version": "2.211.8",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/standardnotes/app",
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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 & 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>
|
||||
)}
|
||||
|
||||
+5
-4
@@ -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>
|
||||
|
||||
+3
-2
@@ -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>
|
||||
|
||||
+5
-4
@@ -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>
|
||||
|
||||
+11
-10
@@ -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
Reference in New Issue
Block a user