mirror of
https://github.com/standardnotes/app
synced 2026-09-14 03:46:01 -04:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fa3fec9ab | ||
|
|
059ee9ea36 | ||
|
|
66d08895fe | ||
|
|
518643dc71 | ||
|
|
5a0c005e8c | ||
|
|
45cdab6f6c | ||
|
|
46245b5569 | ||
|
|
8b103307cb | ||
|
|
a70d1009a3 | ||
|
|
a5b1ff9595 | ||
|
|
d691d7a3b3 | ||
|
|
71675dc6a2 | ||
|
|
6a1b6cef1b | ||
|
|
3d8c12a789 | ||
|
|
370aaecfb8 | ||
|
|
68bfb2ff68 | ||
|
|
a66bb08c3b | ||
|
|
ccde207de8 | ||
|
|
d3262b0f01 | ||
|
|
ffbc984c58 | ||
|
|
979163bae7 | ||
|
|
c928f8a281 | ||
|
|
0e10b347b6 | ||
|
|
caf2c4a876 | ||
|
|
1d22365086 | ||
|
|
bd5ccbfab6 | ||
|
|
46b19d8caf | ||
|
|
a6a0d615c5 | ||
|
|
f3203e2e80 | ||
|
|
e0edabb4b9 | ||
|
|
e68c708628 | ||
|
|
e4f3f26937 | ||
|
|
8575b3e714 | ||
|
|
7c261af230 | ||
|
|
71e20f2a2f | ||
|
|
3855b9f767 | ||
|
|
5c29e22ea4 | ||
|
|
07cb00a815 | ||
|
|
4f2ee4b56f | ||
|
|
90e10c76f7 | ||
|
|
2175c9cdcb | ||
|
|
8c41f0ecc3 | ||
|
|
c8c166d2a0 | ||
|
|
6cacdd8c58 | ||
|
|
b5b6192b60 | ||
|
|
635e2d9150 | ||
|
|
0c23e4d36c | ||
|
|
1e8ce4bee6 | ||
|
|
29368c51b8 |
@@ -45,10 +45,18 @@ jobs:
|
||||
aws-region: us-east-1
|
||||
- name: Deploy static site to S3 bucket
|
||||
run: aws s3 sync packages/web/dist/ s3://app.standardnotes.com --delete
|
||||
- name: Invalidate CloudFront Cache
|
||||
- name: Invalidate CloudFront Cache on .COM
|
||||
uses: chetan/invalidate-cloudfront-action@master
|
||||
env:
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_DISTRIBUTION_ID }}
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_COM_DISTRIBUTION_ID }}
|
||||
PATHS: '/*'
|
||||
AWS_REGION: 'us-east-1'
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
- name: Invalidate CloudFront Cache on .ORG
|
||||
uses: chetan/invalidate-cloudfront-action@master
|
||||
env:
|
||||
DISTRIBUTION: ${{ secrets.WEBAPP_CLOUDFRONT_ORG_DISTRIBUTION_ID }}
|
||||
PATHS: '/*'
|
||||
AWS_REGION: 'us-east-1'
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.15",
|
||||
"version": "1.20.18",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.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.9.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve variance between Super header font sizes and weights ([8b10330](https://github.com/standardnotes/app/commit/8b103307cba41855713b8627cf5d66a6fef6ff52))
|
||||
|
||||
## [1.9.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/blocks-editor",
|
||||
"version": "1.9.6",
|
||||
"version": "1.9.7",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -20,19 +20,20 @@
|
||||
padding-left: 16px;
|
||||
}
|
||||
.Lexical__h1 {
|
||||
font-size: 24px;
|
||||
font-size: 26px;
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
font-weight: 400;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
.Lexical__h2 {
|
||||
font-size: 15px;
|
||||
font-size: 22px;
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
.Lexical__h3 {
|
||||
font-size: 12px;
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
.Lexical__textBold {
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.104.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
# [3.104.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MediaManagerInterface } from './MediaManagerInterface'
|
||||
|
||||
const { systemPreferences } = require('electron')
|
||||
|
||||
export class MediaManager implements MediaManagerInterface {
|
||||
async askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
const permission = systemPreferences.getMediaAccessStatus(type)
|
||||
|
||||
if (permission === 'granted') {
|
||||
return true
|
||||
} else if (permission === 'denied') {
|
||||
return false
|
||||
}
|
||||
|
||||
const granted = await systemPreferences.askForMediaAccess(type)
|
||||
return granted
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface MediaManagerInterface {
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { MenuManagerInterface } from '../Menus/MenuManagerInterface'
|
||||
import { Component, PackageManagerInterface } from '../Packages/PackageManagerInterface'
|
||||
import { SearchManagerInterface } from '../Search/SearchManagerInterface'
|
||||
import { RemoteDataInterface } from './DataInterface'
|
||||
import { MediaManagerInterface } from '../Media/MediaManagerInterface'
|
||||
|
||||
/**
|
||||
* Read https://github.com/electron/remote to understand how electron/remote works.
|
||||
@@ -34,6 +35,7 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
private data: RemoteDataInterface,
|
||||
private menus: MenuManagerInterface,
|
||||
private fileBackups: FileBackupsDevice,
|
||||
private media: MediaManagerInterface,
|
||||
) {}
|
||||
|
||||
get exposableValue(): CrossProcessBridge {
|
||||
@@ -73,6 +75,7 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
openFileBackup: this.openFileBackup.bind(this),
|
||||
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
|
||||
readNextChunk: this.readNextChunk.bind(this),
|
||||
askForMediaAccess: this.askForMediaAccess.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,4 +226,8 @@ export class RemoteBridge implements CrossProcessBridge {
|
||||
public openFileBackup(record: FileBackupRecord): Promise<void> {
|
||||
return this.fileBackups.openFileBackup(record)
|
||||
}
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
return this.media.askForMediaAccess(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createBackupsManager } from './Backups/BackupsManager'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { FilesBackupManager } from './FileBackups/FileBackupsManager'
|
||||
import { Keychain } from './Keychain/Keychain'
|
||||
import { MediaManager } from './Media/MediaManager'
|
||||
import { MenuManagerInterface } from './Menus/MenuManagerInterface'
|
||||
import { buildContextMenu, createMenuManager } from './Menus/Menus'
|
||||
import { initializePackageManager } from './Packages/PackageManager'
|
||||
@@ -74,6 +75,7 @@ export async function createWindowState({
|
||||
},
|
||||
services.menuManager,
|
||||
services.fileBackupsManager,
|
||||
services.mediaManager,
|
||||
)
|
||||
|
||||
const shouldOpenUrl = (url: string) => url.startsWith('http') || url.startsWith('mailto')
|
||||
@@ -203,6 +205,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
const updateManager = setupUpdates(window, appState, backupsManager)
|
||||
const trayManager = createTrayManager(window, appState.store)
|
||||
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
|
||||
const mediaManager = new MediaManager()
|
||||
|
||||
if (isTesting()) {
|
||||
handleTestMessage(MessageType.SpellCheckerManager, () => spellcheckerManager)
|
||||
@@ -228,6 +231,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
|
||||
packageManager,
|
||||
searchManager,
|
||||
fileBackupsManager,
|
||||
mediaManager,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,4 +49,6 @@ export interface CrossProcessBridge extends FileBackupsDevice {
|
||||
onInitialDataLoad(): void
|
||||
|
||||
destroyAllData(): void
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -166,4 +166,8 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
|
||||
isDeviceDestroyed(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
|
||||
return this.remoteBridge.askForMediaAccess(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.104.0",
|
||||
"version": "3.104.15",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
@@ -104,6 +104,9 @@
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "./build/entitlements.mac.inherit.plist",
|
||||
"entitlementsInherit": "./build/entitlements.mac.inherit.plist",
|
||||
"extendInfo": {
|
||||
"NSCameraUsageDescription": "Standard Notes requires access to your camera to enable the Moments feature."
|
||||
},
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [0.2.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/docs
|
||||
|
||||
## [0.2.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/docs
|
||||
|
||||
@@ -90,7 +90,7 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
--sn-stylekit-sans-serif-font: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
|
||||
--sn-stylekit-grey-1: #72767e;
|
||||
--sn-stylekit-grey-2: #bbbec4;
|
||||
--sn-stylekit-grey-3: #dfe1e4;
|
||||
@@ -98,7 +98,7 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
--sn-stylekit-grey-4-opacity-variant: #bbbec43d;
|
||||
--sn-stylekit-grey-5: #f4f5f7;
|
||||
--sn-stylekit-grey-6: #e5e5e5;
|
||||
|
||||
|
||||
--sn-stylekit-accessory-tint-color-1: #086dd6;
|
||||
--sn-stylekit-accessory-tint-color-2: #ea6595;
|
||||
--sn-stylekit-accessory-tint-color-3: #ebad00;
|
||||
@@ -108,6 +108,53 @@ _Note that font and font sizes do not apply to mobile; only desktop/web._
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Overrides
|
||||
|
||||
```css
|
||||
--modal-background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
--editor-header-bar-background-color: var(--sn-stylekit-background-color);
|
||||
--editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||
--editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-title-bar-border-bottom-color: var(--sn-stylekit-border-color);
|
||||
--editor-title-input-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-pane-background-color: var(--sn-stylekit-background-color);
|
||||
--editor-pane-editor-background-color: var(--sn-stylekit-editor-background-color);
|
||||
--editor-pane-editor-foreground-color: var(--sn-stylekit-editor-foreground-color);
|
||||
--editor-pane-component-stack-item-background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
--text-selection-color: var(--sn-stylekit-info-contrast-color);
|
||||
--text-selection-background-color: var(--sn-stylekit-info-color);
|
||||
|
||||
--note-preview-progress-color: var(--sn-stylekit-info-color);
|
||||
--note-preview-progress-background-color: var(--sn-stylekit-passive-color-4-opacity-variant);
|
||||
|
||||
--note-preview-selected-progress-color: var(--sn-stylekit-secondary-background-color);
|
||||
--note-preview-selected-progress-background-color: var(--sn-stylekit-passive-color-4-opacity-variant);
|
||||
|
||||
--items-column-background-color: var(--sn-stylekit-background-color);
|
||||
--items-column-items-background-color: var(--sn-stylekit-background-color);
|
||||
--items-column-border-left-color: var(--sn-stylekit-border-color);
|
||||
--items-column-border-right-color: var(--sn-stylekit-border-color);
|
||||
--items-column-search-background-color: var(--sn-stylekit-contrast-background-color);
|
||||
--item-cell-selected-background-color: var(--sn-stylekit-contrast-background-color);
|
||||
--item-cell-selected-border-left-color: var(--sn-stylekit-info-color);
|
||||
|
||||
--navigation-column-background-color: var(--sn-stylekit-secondary-background-color);
|
||||
--navigation-section-title-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
--navigation-item-text-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
--navigation-item-count-color: var(--sn-stylekit-neutral-color);
|
||||
--navigation-item-selected-background-color: rgb(253, 253, 253);
|
||||
|
||||
--preferences-navigation-icon-color: var(--sn-stylekit-neutral-color);
|
||||
--preferences-navigation-selected-background-color: var(--sn-stylekit-info-backdrop-color);
|
||||
|
||||
--dropdown-menu-radio-button-inactive-color: var(--sn-stylekit-passive-color-1);
|
||||
|
||||
--panel-resizer-background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||
--link-element-color: var(--sn-stylekit-info-color);
|
||||
```
|
||||
|
||||
In order to get SN to display a dock icon for your theme (a circle in the lower right corner of the app that allows you to quickly toggle themes), add the following payload into the your ext.json file when [publishing your theme](/extensions/publishing):
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
---
|
||||
id: auth
|
||||
title: Auth API
|
||||
sidebar_label: Auth
|
||||
description: Authentication portion of the Standard Notes API.
|
||||
keywords:
|
||||
- standard notes
|
||||
- docs
|
||||
- notes app
|
||||
- end-to-end encryption
|
||||
- sync
|
||||
image: /img/logo.png
|
||||
hide_title: false
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
### POST /auth
|
||||
|
||||
**Registers a new user**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| created | `string` | Integer timestamp representing the date the client generated the account's encryption keys. Example "1622494310383" |
|
||||
| email | `string` | The account email. |
|
||||
| ephemeral | `boolean` | Whether the initial session created for this account is ephemeral ("Stay signed in" unchecked). |
|
||||
| identifier | `string` | Should equal the account email. |
|
||||
| origination | `string` | Should equal "registration" |
|
||||
| password | `string` | The server password generated by the client from the user's master password. |
|
||||
| pw_nonce | `string` | The nonce generated by the client for the user's encryption key. |
|
||||
| version | `string` | The protocol version the client used to generate the user's encryption key. Latest is "004". |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:457b1f4b-88c2-4328-bf32-fe7dd9431d62:WwKgTEDUoEhxECu6",
|
||||
"refresh_token": "1:457b1f4b-88c2-4328-bf32-fe7dd9431d62:G-qEoZsFtoj~RLL-",
|
||||
"access_expiration": 1627678312000,
|
||||
"refresh_expiration": 1654051238000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "registration",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /auth/sign_in
|
||||
|
||||
**Authenticates a user and returns a session.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| email | `string` | The account email. |
|
||||
| ephemeral | `boolean` | Whether the session created for this account is ephemeral ("Stay signed in" unchecked). |
|
||||
| password | `string` | The server password generated by the client from the user's master password. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:ee03808d-dd05-417c-9c87-d471e49bcc49:Q~-UoIpDhdtkii_t",
|
||||
"refresh_token": "1:ee03808d-dd05-417c-9c87-d471e49bcc49:Jn8UWzeHx2H5nZI5",
|
||||
"access_expiration": 1627678779000,
|
||||
"refresh_expiration": 1654051705000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "registration",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /auth/params
|
||||
|
||||
**Queries the parameters used for key generation for an email address. Queried before signing into an account.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| email | `string` | The account email. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"identifier": "[email protected]",
|
||||
"pw_nonce": "d97ed41c581fe8c3e0dce7d2ee72afcb63f9f461ae875bae66e30ecf3d952900",
|
||||
"version": "004"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /auth/change_pw
|
||||
|
||||
**Changes a user's password.**
|
||||
|
||||
| | | |
|
||||
|-------------|---------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115" |
|
||||
| created | `string` | Integer timestamp representing the date the client generated the account's new encryption keys. Example "1622494310383" |
|
||||
| identifier | `string` | The account email. |
|
||||
| origination | `string` | Should equal "password-change" |
|
||||
| current_password | `string` | The old server password generated by the client from the user's master password. |
|
||||
| new_password | `string` | The new server password generated by the client from the user's master password. |
|
||||
| pw_nonce | `string` | The nonce generated by the client for the user's encryption key. |
|
||||
| version | `string` | The protocol version the client used to generate the user's encryption key. Latest is "004". |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"session": {
|
||||
"access_token": "1:27d4fd8f-b730-4e0a-afd3-1600fb466aaa:HMgmZwV5k5ePt0vj",
|
||||
"refresh_token": "1:27d4fd8f-b730-4e0a-afd3-1600fb466aaa:wHuTeOB-qWGP3AE3",
|
||||
"access_expiration": 1627679129000,
|
||||
"refresh_expiration": 1654052055000
|
||||
},
|
||||
"key_params": {
|
||||
"created": "1622494310383",
|
||||
"identifier": "[email protected]",
|
||||
"origination": "password-change",
|
||||
"pw_nonce": "be1974ff6fb1c541aa8c71fd3c66851b6492cf224b661c72daf44e0bef3096bb",
|
||||
"version": "004"
|
||||
},
|
||||
"user": {
|
||||
"uuid": "682f9deb-b75f-4d97-91fa-6fd82a482db1",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sessions
|
||||
|
||||
### Session Model
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|-----------|---------------------------------------------------|
|
||||
| uuid | `string` | Unique identifier of the session |
|
||||
| user_uuid | `string` | Unique identifier of the user |
|
||||
| user_agent | `string` | The user agent used to create the session |
|
||||
| api_version | `string` | The server API version used to create the session |
|
||||
| access_token | `string` | The access token used to authenticate requests |
|
||||
| refresh_token | `string` | The refresh token used to extend tokens |
|
||||
| access_expiration | `datetime` | The expiration time of the access token |
|
||||
| refresh_expiration | `datetime` | The expiration time of the refresh token |
|
||||
| created_at | `datetime` | Date and time of creation of the session |
|
||||
| updated_at | `datetime` | Last updated date and time of the session |
|
||||
|
||||
- Each `session` includes the API version they were created with. This way we can deny sessions for a given API version if we detect a vulnerability with that version in the future
|
||||
- Sessions are created in the following cases:
|
||||
- When a user signs in
|
||||
- When a user registers a new account
|
||||
|
||||
---
|
||||
|
||||
### Authenticated requests
|
||||
|
||||
The `Authorization` request header field is used by clients to make authenticated request. `Bearer` is the only authentication scheme allowed.
|
||||
|
||||
The client must send the `access` token generated by the session, in the `Authorization` header. For example:
|
||||
|
||||
```
|
||||
GET /sessions HTTP/1.1
|
||||
Host: sync.standardnotes.org
|
||||
Authorization: Bearer <access token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Below is a list of endpoints related to session management:
|
||||
|
||||
| Method | URL | Params | Description | Successful response code |
|
||||
|-----------|--------------------------|---------------------|--------------------------------------------------------|--------------------------|
|
||||
|`POST` | /auth/sign_out | *None* | Terminates the current session | `204` |
|
||||
|`DELETE` | /session | **uuid** | Terminates the specified session by UUID | `204` |
|
||||
|`DELETE` | /sessions | *None* | Terminates all sessions, except the current one | `204` |
|
||||
|`GET` | /sessions | *None* | Lists all sessions active sessions | `200` |
|
||||
|`POST` | /session/token/refresh | **refresh_token** | Obtains new pair of `access_token` and `refresh_token` | `200` |
|
||||
|
||||
A successful request to `GET /sessions` returns the following JSON response:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessions": [
|
||||
{
|
||||
"uuid": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
"user_agent": "<product> / <product-version> <comment>",
|
||||
"api_version": "xxxxyyzz",
|
||||
"current": "<boolean>",
|
||||
"created_at": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Obtaining tokens
|
||||
|
||||
Tokens can be obtained every time the user performs any of the following actions:
|
||||
|
||||
1. When a user signs in
|
||||
1. When a user registers an account
|
||||
1. When the tokens are refreshed
|
||||
|
||||
#### Refreshing tokens
|
||||
|
||||
When an expired `access_token` is provided in the `Authorization` HTTP header, the following JSON response is returned:
|
||||
|
||||
HTTP Status Code: `498 Expired Access Token`
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"tag": "expired-access-token",
|
||||
"message": "The provided access token has expired."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To continue accessing resources, the `access_token` must be refreshed. That is, the current `access_token` is replaced with a new one with an extended expiration date.
|
||||
|
||||
To refresh an `access_token`, a valid `refresh_token` is needed. This `refresh_token` must meet the following requirements:
|
||||
|
||||
- It should belong to the session of the `access_token`
|
||||
- It should not be expired
|
||||
|
||||
Since the `refresh_token` is of single-use, a new `refresh_token` is obtained when the `access_token` is refreshed.
|
||||
|
||||
Refreshing tokens is a process that is transparent to the user, meaning that the client will perform the requests to keep valid tokens without user intervention.
|
||||
|
||||
Here's how to refresh tokens:
|
||||
|
||||
1. Send a `POST` request to `/session/token/refresh`. The body should contain a JSON paylaod with the `refresh_token`:
|
||||
```
|
||||
POST /session/token/refresh HTTP/1.1
|
||||
Host: sync.standardnotes.org
|
||||
Authorization: Bearer <access token>
|
||||
|
||||
{
|
||||
"refresh_token": "R_xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
1. The `refresh_token` is validated on the server. Depending of the circumstances, there should be two outcomes:
|
||||
1. The provided `refresh_token` is valid. If so, a new pair of tokens is generated and the following JSON response is returned:
|
||||
```json
|
||||
{
|
||||
"token": "xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
"session": {
|
||||
"refresh_expiration": 1583020800,
|
||||
"refresh_token": "xxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
|
||||
}
|
||||
}
|
||||
```
|
||||
1. The provided `refresh_token` is expired. If so, the following JSON response is returned:
|
||||
|
||||
HTTP Status Code: `400 Bad Request`
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"tag": "expired-refresh-token",
|
||||
"message": "The refresh token has expired."
|
||||
}
|
||||
}
|
||||
```
|
||||
User must start a new session by re-entering their credentials.
|
||||
|
||||
### Expiration
|
||||
|
||||
#### Sessions
|
||||
|
||||
Sessions should be terminated after a period of inactivity. This is for best security practices.
|
||||
|
||||
`Long-lived sessions` are a good choice for our use case, because it can build a better user experience than expiring sessions for a short idle-timeout.
|
||||
|
||||
- A `session` that remains inactive for `1 year` will be terminated, along with all `tokens`
|
||||
|
||||
#### Tokens
|
||||
|
||||
| Name | Type | Expiration |
|
||||
|-----------|-------------|---------------|
|
||||
| `access` | Short-lived | `60 days` |
|
||||
| `refresh` | Long-lived | `1 year` |
|
||||
|
||||
- A `refresh` token is of single-use, while an `access` token can be used as long as it is not expired
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
id: sync
|
||||
title: Sync API
|
||||
sidebar_label: Sync
|
||||
description: Specification for the Standard Notes Sync API.
|
||||
keywords:
|
||||
- standard notes
|
||||
- docs
|
||||
- notes app
|
||||
- end-to-end encryption
|
||||
- sync
|
||||
image: /img/logo.png
|
||||
hide_title: false
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
This document outlines the client-server communication of the Standard Notes ecosystem.
|
||||
|
||||
## Item
|
||||
|
||||
An `Item` is largely the only model that both clients and server are concerned wtih. The `content` field stores an encrypted object that can be anything the client needs to operate.
|
||||
|
||||
Relationships between items are handled by the client, and not the server, and are stored encrypted in the `content` field as `references`.
|
||||
|
||||
Items have the following properties:
|
||||
|
||||
| name | type | description |
|
||||
| ------------ | ----------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| uuid | `string` (or uuid for some databases) | The unique identifier for this item. |
|
||||
| content | `text` | An encrypted string generated by the client. |
|
||||
| content_type | `string` | The type of data contained in the `content` field. (i.e Note, Tag, SN|Component, etc.) |
|
||||
| enc_item_key | `text` | The key used to encrypt `content`, itself also encrypted. |
|
||||
| deleted | `boolean` | Whether the model has been deleted. |
|
||||
| created_at_timestamp | `integer` | Timestamp representing when the item was created, with microsecond precision. |
|
||||
| updated_at_timestamp | `integer` | Timestamp representing when the item was updated _by the server_, with microsecond precision. |
|
||||
|
||||
## Content
|
||||
|
||||
Data generated by the client is stored in the `content` field of the `Item`. An item's `content` is "sticky" and can have any property the client chooses, as well as the following:
|
||||
|
||||
| name | type | description |
|
||||
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| appData | `dictionary` | A domain based dictionary whose content must be preserved between platforms. `appData[domain]` contains data the client stores in the item. |
|
||||
| references | `array` | A metadata array of other `Item` uuids this item is related to and their respective `content_type`. See sample below. |
|
||||
|
||||
`references` array structure:
|
||||
|
||||
```JSON
|
||||
[
|
||||
{uuid: xxxx, content_type: "Tag"},
|
||||
{uuid: xxxxx, content_type: "Tag"},
|
||||
]
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
General:
|
||||
|
||||
1. All requests after initial authentication should be authenticated with a session token in the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <access token>
|
||||
|
||||
```
|
||||
|
||||
### POST items/sync
|
||||
|
||||
| | | |
|
||||
|-------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| api | `string` | The API version to use. Must not be null, otherwise oldest API version used. Latest value is "20200115". |
|
||||
| compute_integrity | `boolean` | Should be false in most cases. Pass true if you plan to read the `integrity_hash` result (integrity can be optionally checked once on app launch). |
|
||||
| items | `array` | An array of Items. |
|
||||
| limit | `integer` | The maximum number of items to return per request. Official clients use 150. |
|
||||
| sync_token | `string` | An opaque token returned by the server from a previous sync request. This lets the server know to pull in only new changes. |
|
||||
| cursor_token | `string` | An opaque pagination token returned by the server from a previous multi-page sync request. Send this value back only if the server returned this value from a previous request. |
|
||||
|
||||
#### Response
|
||||
|
||||
```JSON
|
||||
{
|
||||
"retrieved_items": Array<Item>,
|
||||
"saved_items": Array<Item>,
|
||||
"conflicts": Array<Conflict>,
|
||||
"sync_token": string,
|
||||
"cursor_token": string?,
|
||||
"integrity_hash": string?,
|
||||
}
|
||||
```
|
||||
|
||||
| | |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| retrieved_items | Items that are new to the client or have been modified since last sync and should be merged with local values or created locally. |
|
||||
| saved_items | Items which have been successfully saved. Clients should merge *only* the resulting `updated_at_timestamp` with local values. Do not update local value of `content` from remote `saved_items` value. |
|
||||
| conflicts | Items which have not been saved by the server and returned to client. |
|
||||
| sync_token | An opaque token which should be sent back to server in subsequent sync request. |
|
||||
| cursor_token | An opaque token which should be sent back to server in subsequent sync request (if present). |
|
||||
|
||||
#### Conflicts
|
||||
|
||||
```typescript
|
||||
type Conflict = {
|
||||
server_item?: Item
|
||||
unsaved_item?: Item
|
||||
type: 'sync_conflict' | 'uuid_conflict'
|
||||
}
|
||||
```
|
||||
|
||||
A `sync_conflict` occurs when the item the client is attempting to save has a newer change on the server. The server is able to determine this by reading the `updated_at_timestamp` value from both the incoming payload and the server payload. If the incoming payload's `updated_at_timestamp` != to the server's `updated_at_timestamp` for that item, the save is rejected and returned to client as a `sync_conflict`. These types of conflicts are not uncommon.
|
||||
|
||||
To resolve a `sync_conflict`, the client must choose a winner between server_item and unsaved_item then re-sync. The winning item should have its updated_at_timestamp value set to the server's value.
|
||||
|
||||
A `uuid_conflict` occurs when the UUID the client has choosen for an item for X user already belongs to an item for Y user. This should mostly only occur when a user registers a new account and imports a data backup from another account.
|
||||
|
||||
To resolve a `uuid_conflict`, change the UUID of the item attempting to be saved to a new UUID, then re-sync.
|
||||
|
||||
#### Deletion
|
||||
|
||||
To delete an item permanently, set `deleted` to `true` on the `Item` and sync. When receiving an item that is `deleted`, remove it from the local database immediately.
|
||||
@@ -23,8 +23,6 @@ This documentation explains how to get started with using Standard Notes, publis
|
||||
- [Self-hosting a Syncing Server](/self-hosting/getting-started)
|
||||
- [Developing Extensions](/extensions/intro)
|
||||
- [Client Encryption API](/specification/encryption)
|
||||
- [Sync API](/specification/sync)
|
||||
- [Auth API](/specification/auth)
|
||||
|
||||
## Staying informed
|
||||
|
||||
|
||||
@@ -43,11 +43,6 @@ module.exports = {
|
||||
label: 'Client Encryption API',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
to: '/specification/sync',
|
||||
label: 'Server API',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
to: '/extensions/intro',
|
||||
label: 'Build an Extension',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/docs",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -14,11 +14,7 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
{
|
||||
API: [
|
||||
'specification/encryption',
|
||||
'specification/sync',
|
||||
'specification/auth',
|
||||
],
|
||||
API: ['specification/encryption'],
|
||||
},
|
||||
{
|
||||
Extensions: [
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.19.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.23",
|
||||
"version": "1.19.26",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.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.56.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* add Super note type to list of note types ([#2086](https://github.com/standardnotes/app/issues/2086)) ([caf2c4a](https://github.com/standardnotes/app/commit/caf2c4a87646ab4d1aa7007c39547fd3b16c0c6f))
|
||||
|
||||
## [1.55.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.55.3",
|
||||
"version": "1.56.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -51,4 +51,4 @@ export enum FeatureIdentifier {
|
||||
*/
|
||||
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
|
||||
|
||||
export const ExperimentalFeatures = [FeatureIdentifier.SuperEditor]
|
||||
export const ExperimentalFeatures = []
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ClientFeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
|
||||
export function clientFeatures(): ClientFeatureDescription[] {
|
||||
return [
|
||||
@@ -12,6 +12,15 @@ export function clientFeatures(): ClientFeatureDescription[] {
|
||||
permission_name: PermissionName.TagNesting,
|
||||
description: 'Organize your tags into folders.',
|
||||
},
|
||||
{
|
||||
name: 'Super Notes',
|
||||
identifier: FeatureIdentifier.SuperEditor,
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
permission_name: PermissionName.SuperEditor,
|
||||
description:
|
||||
'Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
},
|
||||
{
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
name: 'Smart Filters',
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import { FeatureIdentifier } from './../Feature/FeatureIdentifier'
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
|
||||
export function experimentalFeatures(): FeatureDescription[] {
|
||||
const superEditor: FeatureDescription = {
|
||||
name: 'Super Notes',
|
||||
identifier: FeatureIdentifier.SuperEditor,
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
permission_name: PermissionName.SuperEditor,
|
||||
description:
|
||||
'A new way to edit notes. Type / to bring up the block selection menu, or @ to embed images or link other tags and notes. Type - then space to start a list, or [] then space to start a checklist. Drag and drop an image or file to embed it in your note.',
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
}
|
||||
|
||||
return [superEditor]
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.27.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.27.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** Fixes issue where file upload doesn't work with immediate biometrics ([#2083](https://github.com/standardnotes/app/issues/2083)) ([6cacdd8](https://github.com/standardnotes/app/commit/6cacdd8c5882ac562c73a706aa4237c53a049f5c))
|
||||
|
||||
## [1.27.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.27.0",
|
||||
"version": "1.27.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -18,16 +18,31 @@ function maximumFileSize(): number {
|
||||
return 50 * 1_000_000
|
||||
}
|
||||
|
||||
const FileInputId = 'classic-reader-file-input'
|
||||
function createFileInputOrReturnExisting(): HTMLInputElement {
|
||||
let fileInput = document.getElementById(FileInputId) as HTMLInputElement
|
||||
if (fileInput) {
|
||||
return fileInput
|
||||
}
|
||||
|
||||
fileInput = document.createElement('input')
|
||||
fileInput.id = FileInputId
|
||||
fileInput.type = 'file'
|
||||
fileInput.className = 'absolute top-0 left-0 -z-50 h-px w-px opacity-0'
|
||||
fileInput.multiple = true
|
||||
document.body.appendChild(fileInput)
|
||||
|
||||
return fileInput
|
||||
}
|
||||
|
||||
function selectFiles(): Promise<File[]> {
|
||||
const input = document.createElement('input') as HTMLInputElement
|
||||
input.type = 'file'
|
||||
input.multiple = true
|
||||
const input = createFileInputOrReturnExisting()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
input.onchange = async (event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const files = []
|
||||
for (const file of target.files as FileList) {
|
||||
for (const file of Array.from(target.files as FileList)) {
|
||||
files.push(file)
|
||||
}
|
||||
resolve(files)
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,74 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.49.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed issue where camera was not accessible for Moments on Android ([ffbc984](https://github.com/standardnotes/app/commit/ffbc984c5851f3cd286cd0a832111ba707bbd8f5))
|
||||
|
||||
## [3.49.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where add button would not be visible at times on iOS ([#2087](https://github.com/standardnotes/app/issues/2087)) ([c928f8a](https://github.com/standardnotes/app/commit/c928f8a281ad4e45c79c6c43f009175e5cbbed46))
|
||||
|
||||
## [3.49.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.49.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** PDF preview causing loop when biometrics are set to Immediate ([#2080](https://github.com/standardnotes/app/issues/2080)) ([0c23e4d](https://github.com/standardnotes/app/commit/0c23e4d36cb46cfe66d47bb32a753eacd37eba63))
|
||||
|
||||
# [3.49.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
## [3.48.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
|
||||
<uses-permission android:name="com.fingerprints.service.ACCESS_FINGERPRINT_MANAGER"/>
|
||||
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.48.7",
|
||||
"version": "3.49.14",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -253,6 +253,9 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
return true
|
||||
}
|
||||
|
||||
const requireInlineMediaPlaybackForMomentsFeature = true
|
||||
const requireMediaUserInteractionForMomentsFeature = false
|
||||
|
||||
return (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
@@ -274,6 +277,8 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
injectedJavaScriptBeforeContentLoaded={injectedJS}
|
||||
bounces={false}
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
allowsInlineMediaPlayback={requireInlineMediaPlaybackForMomentsFeature}
|
||||
mediaPlaybackRequiresUserAction={requireMediaUserInteractionForMomentsFeature}
|
||||
scalesPageToFit={true}
|
||||
/**
|
||||
* This disables the global window scroll but keeps scroll within div elements like lists and textareas.
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.39.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
# [1.39.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
## [1.38.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.38.2",
|
||||
"version": "1.39.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum PrefKey {
|
||||
UpdateSavingStatusIndicator = 'updateSavingStatusIndicator',
|
||||
DarkMode = 'darkMode',
|
||||
DefaultEditorIdentifier = 'defaultEditorIdentifier',
|
||||
MomentsDefaultTagUuid = 'momentsDefaultTagUuid',
|
||||
}
|
||||
|
||||
export enum NewNoteTitleFormat {
|
||||
@@ -103,4 +104,5 @@ export type PrefValue = {
|
||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||
[PrefKey.DarkMode]: boolean
|
||||
[PrefKey.DefaultEditorIdentifier]: EditorIdentifier
|
||||
[PrefKey.MomentsDefaultTagUuid]: string | undefined
|
||||
}
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.23",
|
||||
"version": "1.4.38",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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.12.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.12.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.12.9",
|
||||
"version": "1.12.10",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.50.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.50.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android ([07cb00a](https://github.com/standardnotes/app/commit/07cb00a81505ed880a2b8033b3e69c53dd9b48b2))
|
||||
|
||||
## [1.50.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where Moments camera wouldn't properly start on desktop app ([#2084](https://github.com/standardnotes/app/issues/2084)) ([8c41f0e](https://github.com/standardnotes/app/commit/8c41f0ecc30662c5e2fef72e5685056a8cddce62))
|
||||
|
||||
## [1.50.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where files imported from another account could not be deleted ([#2082](https://github.com/standardnotes/app/issues/2082)) ([b5b6192](https://github.com/standardnotes/app/commit/b5b6192b603b5ce60b913cc7138d30e46aafbe14))
|
||||
|
||||
# [1.50.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
# [1.49.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.49.0",
|
||||
"version": "1.50.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -17,4 +17,5 @@ export interface WebApplicationInterface extends ApplicationInterface {
|
||||
mobileDevice(): MobileDeviceInterface
|
||||
handleAndroidBackButtonPressed(): void
|
||||
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined
|
||||
setAndroidBackHandlerFallbackListener(listener: () => boolean): void
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface WebClientRequiresDesktopMethods extends FileBackupsDevice {
|
||||
downloadBackup(): void | Promise<void>
|
||||
|
||||
get extensionsServerHost(): string
|
||||
|
||||
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean>
|
||||
}
|
||||
|
||||
export interface DesktopClientRequiresWebMethods {
|
||||
|
||||
@@ -145,6 +145,23 @@ describe('fileService', () => {
|
||||
expect(fileService['encryptedCache'].get(file.uuid)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('if file fails to delete, should present alert asking if they want to remove item', async () => {
|
||||
const file = {
|
||||
uuid: '1',
|
||||
decryptedSize: 100_000,
|
||||
} as jest.Mocked<FileItem>
|
||||
|
||||
const alertMock = (alertService.confirm = jest.fn().mockReturnValue(true))
|
||||
const deleteItemMock = (itemManager.setItemToBeDeleted = jest.fn())
|
||||
|
||||
apiService.deleteFile = jest.fn().mockReturnValue({ error: true })
|
||||
|
||||
await fileService.deleteFile(file)
|
||||
|
||||
expect(alertMock).toHaveBeenCalledTimes(1)
|
||||
expect(deleteItemMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should download file from network if no backup', async () => {
|
||||
const file = {
|
||||
uuid: '1',
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
isEncryptedPayload,
|
||||
} from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { spaceSeparatedStrings, UuidGenerator } from '@standardnotes/utils'
|
||||
import { EncryptionProviderInterface, SNItemsKey } from '@standardnotes/encryption'
|
||||
import {
|
||||
DownloadAndDecryptFileOperation,
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
readAndDecryptBackupFileUsingBackupService,
|
||||
BackupServiceInterface,
|
||||
} from '@standardnotes/files'
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
import { AlertService, ButtonType } from '../Alert/AlertService'
|
||||
import { ChallengeServiceInterface } from '../Challenge'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
@@ -244,7 +244,21 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
const result = await this.api.deleteFile(tokenResult)
|
||||
|
||||
if (result.error) {
|
||||
return ClientDisplayableError.FromError(result.error)
|
||||
const deleteAnyway = await this.alertService.confirm(
|
||||
spaceSeparatedStrings(
|
||||
'This file could not be deleted from the server, possibly because you are attempting to delete a file item',
|
||||
'that was imported from another account. Would you like to remove this file item from your account anyway?',
|
||||
"If you're sure the file is yours and still exists on the server, do not choose this option,",
|
||||
'and instead try to delete it again.',
|
||||
),
|
||||
'Unable to Delete',
|
||||
'Delete Anyway',
|
||||
ButtonType.Danger,
|
||||
)
|
||||
|
||||
if (!deleteAnyway) {
|
||||
return ClientDisplayableError.FromError(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
await this.itemManager.setItemToBeDeleted(file)
|
||||
|
||||
@@ -40,6 +40,7 @@ export enum StorageKey {
|
||||
CodeVerifier = 'code_verifier',
|
||||
LaunchPriorityUuids = 'launch_priority_uuids',
|
||||
LastReadChangelogVersion = 'last_read_changelog_version',
|
||||
MomentsEnabled = 'moments_enabled',
|
||||
}
|
||||
|
||||
export enum NonwrappedStorageKey {
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.156.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* add Super note type to list of note types ([#2086](https://github.com/standardnotes/app/issues/2086)) ([caf2c4a](https://github.com/standardnotes/app/commit/caf2c4a87646ab4d1aa7007c39547fd3b16c0c6f))
|
||||
|
||||
## [2.155.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.155.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.155.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.155.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.155.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -197,7 +197,7 @@ describe('featuresService', () => {
|
||||
|
||||
describe('loadUserRoles()', () => {
|
||||
it('retrieves user roles and features from storage', async () => {
|
||||
await createService().initializeFromDisk()
|
||||
createService().initializeFromDisk()
|
||||
expect(storageService.getValue).toHaveBeenCalledWith(StorageKey.UserRoles, undefined, [])
|
||||
expect(storageService.getValue).toHaveBeenCalledWith(StorageKey.UserFeatures, undefined, [])
|
||||
})
|
||||
@@ -576,6 +576,14 @@ describe('featuresService', () => {
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('availableInRoles-based features', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.ProUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('third party feature status', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
|
||||
@@ -506,13 +506,11 @@ export class SNFeaturesService
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
if (this.isExperimentalFeature(featureId)) {
|
||||
const nativeFeature = FeaturesImports.FindNativeFeature(featureId)
|
||||
if (nativeFeature) {
|
||||
const hasRole = this.roles.some((role) => nativeFeature.availableInRoles?.includes(role))
|
||||
if (hasRole) {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
const nativeFeature = FeaturesImports.FindNativeFeature(featureId)
|
||||
if (nativeFeature && nativeFeature.availableInRoles) {
|
||||
const hasRole = this.roles.some((role) => nativeFeature.availableInRoles?.includes(role))
|
||||
if (hasRole) {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +523,7 @@ export class SNFeaturesService
|
||||
}
|
||||
}
|
||||
|
||||
const isThirdParty = FeaturesImports.FindNativeFeature(featureId) == undefined
|
||||
const isThirdParty = nativeFeature == undefined
|
||||
if (isThirdParty) {
|
||||
const component = this.itemManager
|
||||
.getDisplayableComponents()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.155.0",
|
||||
"version": "2.156.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
"lint:eslint": "eslint --ext .ts lib/",
|
||||
"lint:fix": "eslint --fix --ext .ts lib/",
|
||||
"lint:tsc": "tsc --noEmit --emitDeclarationOnly false --project lib/tsconfig.json",
|
||||
"test": "jest spec --coverage",
|
||||
"test": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [1.19.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed workspace menu item rename issue ([45cdab6](https://github.com/standardnotes/app/commit/45cdab6f6c0416e0a01a66e4b4983ff0a52c1afc))
|
||||
|
||||
## [1.19.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.19.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) ([e68c708](https://github.com/standardnotes/app/commit/e68c7086282c5acaaacdfd0030148f1152b98842))
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences ([7c261af](https://github.com/standardnotes/app/commit/7c261af23018c398d24d695fb024df83928b8189))
|
||||
|
||||
## [1.17.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.17.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.17.7",
|
||||
"version": "1.19.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ export const DELETE_NOTE_KEYBOARD_COMMAND = createKeyboardCommand('DELETE_NOTE_K
|
||||
export const TAB_COMMAND = createKeyboardCommand('PLAIN_EDITOR_INSERT_TAB_KEYBOARD_COMMAND')
|
||||
export const ESCAPE_COMMAND = createKeyboardCommand('ESCAPE_COMMAND')
|
||||
export const TOGGLE_FOCUS_MODE_COMMAND = createKeyboardCommand('TOGGLE_FOCUS_MODE_COMMAND')
|
||||
export const TOGGLE_DARK_MODE_COMMAND = createKeyboardCommand('TOGGLE_DARK_MODE_COMMAND')
|
||||
export const CHANGE_EDITOR_COMMAND = createKeyboardCommand('CHANGE_EDITOR_COMMAND')
|
||||
export const FOCUS_TAGS_INPUT_COMMAND = createKeyboardCommand('FOCUS_TAGS_INPUT_COMMAND')
|
||||
export const CREATE_NEW_TAG_COMMAND = createKeyboardCommand('CREATE_NEW_TAG_COMMAND')
|
||||
@@ -28,3 +29,4 @@ export const SUPER_SHOW_MARKDOWN_PREVIEW = createKeyboardCommand('SUPER_SHOW_MAR
|
||||
export const SUPER_EXPORT_JSON = createKeyboardCommand('SUPER_EXPORT_JSON')
|
||||
export const SUPER_EXPORT_MARKDOWN = createKeyboardCommand('SUPER_EXPORT_MARKDOWN')
|
||||
export const SUPER_EXPORT_HTML = createKeyboardCommand('SUPER_EXPORT_HTML')
|
||||
export const OPEN_PREFERENCES_COMMAND = createKeyboardCommand('OPEN_PREFERENCES_COMMAND')
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum KeyboardKey {
|
||||
Escape = 'Escape',
|
||||
Home = 'Home',
|
||||
End = 'End',
|
||||
Space = ' ',
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
STAR_NOTE_COMMAND,
|
||||
PIN_NOTE_COMMAND,
|
||||
SUPER_SHOW_MARKDOWN_PREVIEW,
|
||||
OPEN_PREFERENCES_COMMAND,
|
||||
TOGGLE_DARK_MODE_COMMAND,
|
||||
} from './KeyboardCommands'
|
||||
import { KeyboardKey } from './KeyboardKey'
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
@@ -92,6 +94,12 @@ export function getKeyboardShortcuts(platform: Platform, _environment: Environme
|
||||
key: 'f',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: TOGGLE_DARK_MODE_COMMAND,
|
||||
key: 'd',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: CHANGE_EDITOR_COMMAND,
|
||||
key: '/',
|
||||
@@ -139,5 +147,11 @@ export function getKeyboardShortcuts(platform: Platform, _environment: Environme
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: OPEN_PREFERENCES_COMMAND,
|
||||
key: ',',
|
||||
modifiers: [primaryModifier],
|
||||
preventDefault: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ export class RouteService
|
||||
return new RouteParser(window.location.href)
|
||||
}
|
||||
|
||||
get isDotOrg(): boolean {
|
||||
return window.location.host === 'app.standardnotes.org'
|
||||
}
|
||||
|
||||
removeQueryParameterFromURL(param: RootQueryParam): void {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
urlSearchParams.delete(param)
|
||||
|
||||
@@ -5,4 +5,5 @@ export interface RouteServiceInterface {
|
||||
deinit(): void
|
||||
getRoute(): RouteParserInterface
|
||||
removeQueryParameterFromURL(param: RootQueryParam): void
|
||||
get isDotOrg(): boolean
|
||||
}
|
||||
|
||||
@@ -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.14.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where files imported from another account could not be deleted ([#2082](https://github.com/standardnotes/app/issues/2082)) ([b5b6192](https://github.com/standardnotes/app/commit/b5b6192b603b5ce60b913cc7138d30e46aafbe14))
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/utils",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -683,3 +683,7 @@ export function assert(value: unknown): asserts value {
|
||||
export function useBoolean(value: boolean | undefined, defaultValue: boolean): boolean {
|
||||
return value != undefined ? value : defaultValue
|
||||
}
|
||||
|
||||
export function spaceSeparatedStrings(...strings: string[]): string {
|
||||
return strings.join(' ')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,110 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.121.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue about note UUID not being selectable in menu ([5a0c005](https://github.com/standardnotes/app/commit/5a0c005e8c0373e2f47fa613fc5bfec0e84cdb37))
|
||||
* Fixed workspace menu item rename issue ([45cdab6](https://github.com/standardnotes/app/commit/45cdab6f6c0416e0a01a66e4b4983ff0a52c1afc))
|
||||
|
||||
# [3.121.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where Dark theme could be called 'Focus' in Appearance preferences ([d691d7a](https://github.com/standardnotes/app/commit/d691d7a3b34f7f4249138baa4c3f27b79a9f8e6c))
|
||||
* Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu ([a5b1ff9](https://github.com/standardnotes/app/commit/a5b1ff95957a9d407663d28429785457aef3a1b4))
|
||||
|
||||
### Features
|
||||
|
||||
* add Indent/Outdent options in Super when on mobile ([a70d100](https://github.com/standardnotes/app/commit/a70d1009a3d8d6be1395bf148a46c3cb2260dcdc))
|
||||
|
||||
## [3.120.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed error when trying to upload file ([6a1b6ce](https://github.com/standardnotes/app/commit/6a1b6cef1bab42d9de1a0a396c38cd899529648a))
|
||||
|
||||
## [3.120.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixed issue where popover menus would open with incorrect positioning on desktop ([a66bb08](https://github.com/standardnotes/app/commit/a66bb08c3b715f942480c7006fdea213a0bfc956))
|
||||
|
||||
## [3.120.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where add button would not be visible at times on iOS ([#2087](https://github.com/standardnotes/app/issues/2087)) ([c928f8a](https://github.com/standardnotes/app/commit/c928f8a281ad4e45c79c6c43f009175e5cbbed46))
|
||||
|
||||
# [3.120.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where first change was ignored with Super notes ([1d22365](https://github.com/standardnotes/app/commit/1d22365086ffca3efa46143783b724fcde3c1681))
|
||||
|
||||
### Features
|
||||
|
||||
* add Super note type to list of note types ([#2086](https://github.com/standardnotes/app/issues/2086)) ([caf2c4a](https://github.com/standardnotes/app/commit/caf2c4a87646ab4d1aa7007c39547fd3b16c0c6f))
|
||||
|
||||
## [3.119.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where toggled-off panes would reappear after making selection ([a6a0d61](https://github.com/standardnotes/app/commit/a6a0d615c58e6a70737654f6c6c75111a23bfb7e))
|
||||
|
||||
# [3.119.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) ([e68c708](https://github.com/standardnotes/app/commit/e68c7086282c5acaaacdfd0030148f1152b98842))
|
||||
|
||||
# [3.118.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed purchase flow padding ([71e20f2](https://github.com/standardnotes/app/commit/71e20f2a2f7422ced99311676181db4b3113fa94))
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences ([7c261af](https://github.com/standardnotes/app/commit/7c261af23018c398d24d695fb024df83928b8189))
|
||||
* Show preferences keyboard shortcut in tooltip ([8575b3e](https://github.com/standardnotes/app/commit/8575b3e71487f468102ade9c1fb8bf966231e6e1))
|
||||
|
||||
## [3.117.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android ([07cb00a](https://github.com/standardnotes/app/commit/07cb00a81505ed880a2b8033b3e69c53dd9b48b2))
|
||||
|
||||
## [3.117.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.117.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where Moments camera wouldn't properly start on desktop app ([#2084](https://github.com/standardnotes/app/issues/2084)) ([8c41f0e](https://github.com/standardnotes/app/commit/8c41f0ecc30662c5e2fef72e5685056a8cddce62))
|
||||
|
||||
## [3.117.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes issue where files imported from another account could not be deleted ([#2082](https://github.com/standardnotes/app/issues/2082)) ([b5b6192](https://github.com/standardnotes/app/commit/b5b6192b603b5ce60b913cc7138d30e46aafbe14))
|
||||
* **mobile:** Fixes issue where file upload doesn't work with immediate biometrics ([#2083](https://github.com/standardnotes/app/issues/2083)) ([6cacdd8](https://github.com/standardnotes/app/commit/6cacdd8c5882ac562c73a706aa4237c53a049f5c))
|
||||
|
||||
## [3.117.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** PDF preview causing loop when biometrics are set to Immediate ([#2080](https://github.com/standardnotes/app/issues/2080)) ([0c23e4d](https://github.com/standardnotes/app/commit/0c23e4d36cb46cfe66d47bb32a753eacd37eba63))
|
||||
|
||||
# [3.117.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)
|
||||
|
||||
### Features
|
||||
|
||||
* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))
|
||||
|
||||
# [3.116.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,5 +1,232 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.121.1",
|
||||
"title": "[3.121.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue about note UUID not being selectable in menu ([5a0c005](https://github.com/standardnotes/app/commit/5a0c005e8c0373e2f47fa613fc5bfec0e84cdb37))\n* Fixed workspace menu item rename issue ([45cdab6](https://github.com/standardnotes/app/commit/45cdab6f6c0416e0a01a66e4b4983ff0a52c1afc))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue about note UUID not being selectable in menu (5a0c005)",
|
||||
"Fixed workspace menu item rename issue (45cdab6)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue about note UUID not being selectable in menu (5a0c005)",
|
||||
"Fixed workspace menu item rename issue (45cdab6)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.121.0",
|
||||
"title": "[3.121.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-07)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed issue where Dark theme could be called 'Focus' in Appearance preferences ([d691d7a](https://github.com/standardnotes/app/commit/d691d7a3b34f7f4249138baa4c3f27b79a9f8e6c))\n* Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu ([a5b1ff9](https://github.com/standardnotes/app/commit/a5b1ff95957a9d407663d28429785457aef3a1b4))\n\n### Features\n\n* add Indent/Outdent options in Super when on mobile ([a70d100](https://github.com/standardnotes/app/commit/a70d1009a3d8d6be1395bf148a46c3cb2260dcdc))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed issue where Dark theme could be called 'Focus' in Appearance preferences (d691d7a)",
|
||||
"Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu (a5b1ff9)",
|
||||
"add Indent/Outdent options in Super when on mobile (a70d100)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed issue where Dark theme could be called 'Focus' in Appearance preferences (d691d7a)",
|
||||
"Fixed issue where entering a space or punctuation in Super autocomplete would dismiss menu (a5b1ff9)"
|
||||
],
|
||||
"Features": [
|
||||
"add Indent/Outdent options in Super when on mobile (a70d100)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.120.3",
|
||||
"title": "[3.120.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed error when trying to upload file ([6a1b6ce](https://github.com/standardnotes/app/commit/6a1b6cef1bab42d9de1a0a396c38cd899529648a))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed error when trying to upload file (6a1b6ce)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed error when trying to upload file (6a1b6ce)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.120.2",
|
||||
"title": "[3.120.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-06)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixed issue where popover menus would open with incorrect positioning on desktop ([a66bb08](https://github.com/standardnotes/app/commit/a66bb08c3b715f942480c7006fdea213a0bfc956))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixed issue where popover menus would open with incorrect positioning on desktop (a66bb08)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixed issue where popover menus would open with incorrect positioning on desktop (a66bb08)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.120.1",
|
||||
"title": "[3.120.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixes issue where add button would not be visible at times on iOS ([#2087](https://github.com/standardnotes/app/issues/2087)) ([c928f8a](https://github.com/standardnotes/app/commit/c928f8a281ad4e45c79c6c43f009175e5cbbed46))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixes issue where add button would not be visible at times on iOS (#2087) (c928f8a)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixes issue where add button would not be visible at times on iOS (#2087) (c928f8a)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.120.0",
|
||||
"title": "[3.120.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixes issue where first change was ignored with Super notes ([1d22365](https://github.com/standardnotes/app/commit/1d22365086ffca3efa46143783b724fcde3c1681))\n\n### Features\n\n* add Super note type to list of note types ([#2086](https://github.com/standardnotes/app/issues/2086)) ([caf2c4a](https://github.com/standardnotes/app/commit/caf2c4a87646ab4d1aa7007c39547fd3b16c0c6f))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixes issue where first change was ignored with Super notes (1d22365)",
|
||||
"add Super note type to list of note types (#2086) (caf2c4a)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixes issue where first change was ignored with Super notes (1d22365)"
|
||||
],
|
||||
"Features": [
|
||||
"add Super note type to list of note types (#2086) (caf2c4a)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.119.1",
|
||||
"title": "[3.119.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixes issue where toggled-off panes would reappear after making selection ([a6a0d61](https://github.com/standardnotes/app/commit/a6a0d615c58e6a70737654f6c6c75111a23bfb7e))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixes issue where toggled-off panes would reappear after making selection (a6a0d61)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixes issue where toggled-off panes would reappear after making selection (a6a0d61)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.119.0",
|
||||
"title": "[3.119.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) ([e68c708](https://github.com/standardnotes/app/commit/e68c7086282c5acaaacdfd0030148f1152b98842))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) (e68c708)"
|
||||
],
|
||||
"Features": [
|
||||
"Added keyboard shortcut to toggle dark theme (Ctrl/Cmd+Shift+D) (e68c708)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.118.0",
|
||||
"title": "[3.118.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-05)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* Fixed purchase flow padding ([71e20f2](https://github.com/standardnotes/app/commit/71e20f2a2f7422ced99311676181db4b3113fa94))\n\n### Features\n\n* Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences ([7c261af](https://github.com/standardnotes/app/commit/7c261af23018c398d24d695fb024df83928b8189))\n* Show preferences keyboard shortcut in tooltip ([8575b3e](https://github.com/standardnotes/app/commit/8575b3e71487f468102ade9c1fb8bf966231e6e1))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Fixed purchase flow padding (71e20f2)",
|
||||
"Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences (7c261af)",
|
||||
"Show preferences keyboard shortcut in tooltip (8575b3e)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"Fixed purchase flow padding (71e20f2)"
|
||||
],
|
||||
"Features": [
|
||||
"Added keyboard shortcut (Ctrl/Cmd+,) for opening preferences (7c261af)",
|
||||
"Show preferences keyboard shortcut in tooltip (8575b3e)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.5",
|
||||
"title": "[3.117.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* **mobile:** Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android ([07cb00a](https://github.com/standardnotes/app/commit/07cb00a81505ed880a2b8033b3e69c53dd9b48b2))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"mobile: Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android (07cb00a)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"mobile: Fixed issue where pressing the back button on editor screen wouldn't switch back to the items list on Android (07cb00a)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.4",
|
||||
"title": "[3.117.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-04)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.3",
|
||||
"title": "[3.117.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixes issue where Moments camera wouldn't properly start on desktop app ([#2084](https://github.com/standardnotes/app/issues/2084)) ([8c41f0e](https://github.com/standardnotes/app/commit/8c41f0ecc30662c5e2fef72e5685056a8cddce62))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixes issue where Moments camera wouldn't properly start on desktop app (#2084) (8c41f0e)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixes issue where Moments camera wouldn't properly start on desktop app (#2084) (8c41f0e)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.2",
|
||||
"title": "[3.117.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* fixes issue where files imported from another account could not be deleted ([#2082](https://github.com/standardnotes/app/issues/2082)) ([b5b6192](https://github.com/standardnotes/app/commit/b5b6192b603b5ce60b913cc7138d30e46aafbe14))\n* **mobile:** Fixes issue where file upload doesn't work with immediate biometrics ([#2083](https://github.com/standardnotes/app/issues/2083)) ([6cacdd8](https://github.com/standardnotes/app/commit/6cacdd8c5882ac562c73a706aa4237c53a049f5c))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"fixes issue where files imported from another account could not be deleted (#2082) (b5b6192)",
|
||||
"mobile: Fixes issue where file upload doesn't work with immediate biometrics (#2083) (6cacdd8)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"fixes issue where files imported from another account could not be deleted (#2082) (b5b6192)",
|
||||
"mobile: Fixes issue where file upload doesn't work with immediate biometrics (#2083) (6cacdd8)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.1",
|
||||
"title": "[3.117.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)",
|
||||
"date": null,
|
||||
"body": "### Bug Fixes\n\n* **mobile:** PDF preview causing loop when biometrics are set to Immediate ([#2080](https://github.com/standardnotes/app/issues/2080)) ([0c23e4d](https://github.com/standardnotes/app/commit/0c23e4d36cb46cfe66d47bb32a753eacd37eba63))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"mobile: PDF preview causing loop when biometrics are set to Immediate (#2080) (0c23e4d)"
|
||||
],
|
||||
"Bug Fixes": [
|
||||
"mobile: PDF preview causing loop when biometrics are set to Immediate (#2080) (0c23e4d)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.117.0",
|
||||
"title": "[3.117.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-02)",
|
||||
"date": null,
|
||||
"body": "### Features\n\n* Moments: your personal photo journal, now available in Labs ([#2079](https://github.com/standardnotes/app/issues/2079)) ([29368c5](https://github.com/standardnotes/app/commit/29368c51b801331dd73d8cbee9746abadf2e1120))",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Moments: your personal photo journal, now available in Labs (#2079) (29368c5)"
|
||||
],
|
||||
"Features": [
|
||||
"Moments: your personal photo journal, now available in Labs (#2079) (29368c5)"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.116.0",
|
||||
"title": "[3.116.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-01)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.116.0",
|
||||
"version": "3.121.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes",
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('web application', () => {
|
||||
beforeEach(() => {
|
||||
const identifier = '123'
|
||||
|
||||
window.matchMedia = jest.fn().mockReturnValue({ matches: true, addListener: jest.fn() })
|
||||
window.matchMedia = jest.fn().mockReturnValue({ matches: false, addListener: jest.fn() })
|
||||
|
||||
const device = {
|
||||
environment: Environment.Desktop,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@standardnotes/snjs'
|
||||
import { makeObservable, observable } from 'mobx'
|
||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import { isAndroid, isDesktopApplication, isIOS } from '@/Utils'
|
||||
import { DesktopManager } from './Device/DesktopManager'
|
||||
import {
|
||||
ArchiveManager,
|
||||
@@ -45,6 +45,7 @@ import { WebServices } from './WebServices'
|
||||
import { FeatureName } from '@/Controllers/FeatureName'
|
||||
import { ItemGroupController } from '@/Components/NoteView/Controller/ItemGroupController'
|
||||
import { VisibilityObserver } from './VisibilityObserver'
|
||||
import { MomentsService } from '@/Controllers/Moments/MomentsService'
|
||||
|
||||
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
||||
|
||||
@@ -98,6 +99,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
: undefined
|
||||
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
||||
this.webServices.changelogService = new ChangelogService(this.environment, this.storage)
|
||||
this.webServices.momentsService = new MomentsService(
|
||||
this,
|
||||
this.webServices.viewControllerManager.filesController,
|
||||
internalEventBus,
|
||||
)
|
||||
|
||||
if (this.isNativeMobileWeb()) {
|
||||
this.mobileWebReceiver = new MobileWebReceiver(this)
|
||||
@@ -196,10 +202,22 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return this.webServices.viewControllerManager.paneController
|
||||
}
|
||||
|
||||
public get linkingController() {
|
||||
return this.webServices.viewControllerManager.linkingController
|
||||
}
|
||||
|
||||
public get changelogService() {
|
||||
return this.webServices.changelogService
|
||||
}
|
||||
|
||||
public get momentsService() {
|
||||
return this.webServices.momentsService
|
||||
}
|
||||
|
||||
public get featuresController() {
|
||||
return this.getViewControllerManager().featuresController
|
||||
}
|
||||
|
||||
public get desktopDevice(): DesktopDeviceInterface | undefined {
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
return this.deviceInterface
|
||||
@@ -212,6 +230,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return this.isNativeMobileWeb() && this.platform === Platform.Ios
|
||||
}
|
||||
|
||||
get isMobileDevice() {
|
||||
return this.isNativeMobileWeb() || isIOS() || isAndroid()
|
||||
}
|
||||
|
||||
get hideOutboundSubscriptionLinks() {
|
||||
return this.isNativeIOS()
|
||||
}
|
||||
@@ -347,6 +369,12 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return
|
||||
}
|
||||
|
||||
setAndroidBackHandlerFallbackListener(listener: () => boolean) {
|
||||
if (typeof this.androidBackHandler !== 'undefined') {
|
||||
this.androidBackHandler.setFallbackListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
isAuthorizedToRenderItem(item: DecryptedItem): boolean {
|
||||
if (item.protected && this.hasProtectionSources()) {
|
||||
return this.hasUnprotectedAccessSession()
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
KeyboardService,
|
||||
ThemeManager,
|
||||
} from '@standardnotes/ui-services'
|
||||
import { MomentsService } from '@/Controllers/Moments/MomentsService'
|
||||
|
||||
export type WebServices = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -16,4 +17,5 @@ export type WebServices = {
|
||||
themeService: ThemeManager
|
||||
keyboardService: KeyboardService
|
||||
changelogService: ChangelogServiceInterface
|
||||
momentsService: MomentsService
|
||||
}
|
||||
|
||||
+47
-45
@@ -51,54 +51,56 @@ const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
const handleInputBlur: FocusEventHandler<HTMLInputElement> = useCallback(() => {
|
||||
renameDescriptor(inputValue)
|
||||
setIsRenaming(false)
|
||||
setInputValue('')
|
||||
}, [inputValue, renameDescriptor])
|
||||
|
||||
return (
|
||||
<MenuRadioButtonItem
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onClick}
|
||||
checked={descriptor.primary}
|
||||
>
|
||||
<div className="ml-2 flex w-full items-center justify-between">
|
||||
{isRenaming ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
) : (
|
||||
<div>{descriptor.label}</div>
|
||||
)}
|
||||
{descriptor.primary && !hideOptions && (
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
role="button"
|
||||
className="mr-3 h-5 w-5 cursor-pointer border-0 bg-transparent p-0 hover:bg-contrast"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsRenaming((isRenaming) => !isRenaming)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="text-neutral" size="medium" />
|
||||
</a>
|
||||
<a
|
||||
role="button"
|
||||
className="h-5 w-5 cursor-pointer border-0 bg-transparent p-0 hover:bg-contrast"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDelete()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="text-danger" size="medium" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MenuRadioButtonItem>
|
||||
<div className="relative">
|
||||
<MenuRadioButtonItem
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onClick}
|
||||
checked={descriptor.primary}
|
||||
>
|
||||
<div className="ml-2 flex w-full items-center justify-between">
|
||||
{!isRenaming && <div>{descriptor.label}</div>}
|
||||
{descriptor.primary && !hideOptions && (
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
role="button"
|
||||
className="mr-3 flex h-5 w-5 cursor-pointer items-center justify-center border-0 bg-transparent p-0 hover:bg-contrast"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsRenaming((isRenaming) => !isRenaming)
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="text-neutral" size="medium" />
|
||||
</a>
|
||||
<a
|
||||
role="button"
|
||||
className="flex h-5 w-5 cursor-pointer items-center justify-center border-0 bg-transparent p-0 hover:bg-contrast"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDelete()
|
||||
}}
|
||||
>
|
||||
<Icon type="trash" className="text-danger" size="medium" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MenuRadioButtonItem>
|
||||
{isRenaming && (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="absolute top-1/2 left-11 -translate-y-1/2 bg-default"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
|
||||
import ApplicationProvider from '../ApplicationProvider'
|
||||
import CommandProvider from '../CommandProvider'
|
||||
import PanesSystemComponent from '../Panes/PanesSystemComponent'
|
||||
import DotOrgNotice from './DotOrgNotice'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -230,6 +231,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
viewControllerManager={viewControllerManager}
|
||||
/>
|
||||
</>
|
||||
{application.routeService.isDotOrg && <DotOrgNotice />}
|
||||
</div>
|
||||
</PremiumModalProvider>
|
||||
</ResponsivePaneProvider>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
const DotOrgNotice: FunctionComponent = () => {
|
||||
return (
|
||||
<div className="align-center z-modal flex h-30 w-full w-full items-center bg-danger text-center text-info-contrast">
|
||||
<div className="w-full text-center text-xl font-bold">
|
||||
app.standardnotes.org is no longer maintained. Please switch to{' '}
|
||||
<a className="underline" href="https://app.standardnotes.com">
|
||||
app.standardnotes.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DotOrgNotice
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Ref, forwardRef, ReactNode, ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
type ButtonStyle = 'default' | 'contrast' | 'neutral' | 'info' | 'warning' | 'danger' | 'success'
|
||||
export type ButtonStyle = 'default' | 'contrast' | 'neutral' | 'info' | 'warning' | 'danger' | 'success'
|
||||
|
||||
const getColorsForNormalVariant = (style: ButtonStyle) => {
|
||||
switch (style) {
|
||||
@@ -21,7 +21,7 @@ const getColorsForNormalVariant = (style: ButtonStyle) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getColorsForPrimaryVariant = (style: ButtonStyle) => {
|
||||
export const getColorsForPrimaryVariant = (style: ButtonStyle) => {
|
||||
switch (style) {
|
||||
case 'default':
|
||||
return 'bg-default text-foreground'
|
||||
|
||||
@@ -28,6 +28,7 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
||||
})
|
||||
const [selectedEditorIcon, selectedEditorIconTint] = getIconAndTintForNoteType(
|
||||
note?.noteType || selectedEditor?.package_info.note_type,
|
||||
true,
|
||||
)
|
||||
const [isClickOutsideDisabled, setIsClickOutsideDisabled] = useState(false)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { reloadFont } from '../NoteView/FontFunctions'
|
||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
import { SuperNoteImporter } from '../NoteView/SuperEditor/SuperNoteImporter'
|
||||
import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem'
|
||||
import { Pill } from '../Preferences/PreferencesComponents/Content'
|
||||
|
||||
type ChangeEditorMenuProps = {
|
||||
application: WebApplication
|
||||
@@ -188,17 +189,24 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
const onClickEditorItem = () => {
|
||||
selectItem(item).catch(console.error)
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuRadioButtonItem
|
||||
key={item.name}
|
||||
onClick={onClickEditorItem}
|
||||
className={'flex-row-reverse py-2'}
|
||||
className={'flex-row-reversed py-2'}
|
||||
checked={item.isEntitled ? isSelected(item) : false}
|
||||
info={item.description}
|
||||
>
|
||||
<div className="flex flex-grow items-center justify-between">
|
||||
<div className={`flex items-center ${group.featured ? 'font-bold' : ''}`}>
|
||||
{group.icon && <Icon type={group.icon} className={`mr-2 ${group.iconClassName}`} />}
|
||||
{item.name}
|
||||
{item.isLabs && (
|
||||
<Pill className="py-0.5 px-1.5" style="success">
|
||||
Labs
|
||||
</Pill>
|
||||
)}
|
||||
</div>
|
||||
{!item.isEntitled && (
|
||||
<Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />
|
||||
|
||||
@@ -11,7 +11,7 @@ import { WebApplication } from '@/Application/Application'
|
||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||
import { FileItem, PrefKey, WebAppEvent } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { forwardRef, useCallback, useEffect, useMemo } from 'react'
|
||||
import ContentList from '@/Components/ContentListView/ContentList'
|
||||
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
|
||||
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||
@@ -25,7 +25,6 @@ import { ElementIds } from '@/Constants/ElementIDs'
|
||||
import ContentListHeader from './Header/ContentListHeader'
|
||||
import { AppPaneId } from '../Panes/AppPaneMetadata'
|
||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||
import { StreamingFileReader } from '@standardnotes/filepicker'
|
||||
import SearchBar from '../SearchBar/SearchBar'
|
||||
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
@@ -36,6 +35,8 @@ import { ListableContentItem } from './Types/ListableContentItem'
|
||||
import { FeatureName } from '@/Controllers/FeatureName'
|
||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||
import { useForwardedRef } from '@/Hooks/useForwardedRef'
|
||||
import { isMobileScreen } from '@/Utils'
|
||||
import FloatingAddButton from './FloatingAddButton'
|
||||
|
||||
type Props = {
|
||||
accountMenuController: AccountMenuController
|
||||
@@ -88,7 +89,6 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
isCurrentNoteTemplate,
|
||||
} = itemListController
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const innerRef = useForwardedRef(ref)
|
||||
|
||||
const { addDragTarget, removeDragTarget } = useFileDragNDrop()
|
||||
@@ -116,7 +116,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
}, [selectedTag, application, onPanelWidthLoad])
|
||||
|
||||
const fileDropCallback = useCallback(
|
||||
async (files: FileItem[]) => {
|
||||
async (file: FileItem) => {
|
||||
const currentTag = navigationController.selected
|
||||
|
||||
if (!currentTag) {
|
||||
@@ -128,9 +128,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
return
|
||||
}
|
||||
|
||||
files.forEach(async (file) => {
|
||||
await linkingController.linkItems(file, currentTag)
|
||||
})
|
||||
await linkingController.linkItems(file, currentTag)
|
||||
},
|
||||
[navigationController, linkingController],
|
||||
)
|
||||
@@ -172,12 +170,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
return
|
||||
}
|
||||
|
||||
if (StreamingFileReader.available()) {
|
||||
void filesController.uploadNewFile()
|
||||
return
|
||||
}
|
||||
|
||||
fileInputRef.current?.click()
|
||||
void filesController.selectAndUploadNewFiles()
|
||||
} else {
|
||||
await createNewNote()
|
||||
toggleAppPane(AppPaneId.Editor)
|
||||
@@ -294,25 +287,11 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
aria-label={'Notes & Files'}
|
||||
ref={innerRef}
|
||||
>
|
||||
{isMobileScreen() && (
|
||||
<FloatingAddButton onClick={addNewItem} label={addButtonLabel} style={dailyMode ? 'danger' : 'info'} />
|
||||
)}
|
||||
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
|
||||
<div id="items-title-bar-container">
|
||||
<input
|
||||
type="file"
|
||||
className="absolute top-0 left-0 -z-50 h-px w-px opacity-0"
|
||||
multiple
|
||||
ref={fileInputRef}
|
||||
onChange={(event) => {
|
||||
const files = event.currentTarget.files
|
||||
|
||||
if (!files) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
void filesController.uploadNewFile(files[i])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{selectedTag && (
|
||||
<ContentListHeader
|
||||
application={application}
|
||||
|
||||
@@ -7,13 +7,13 @@ import ListItemTags from './ListItemTags'
|
||||
import ListItemMetadata from './ListItemMetadata'
|
||||
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||
import { AppPaneId } from '../Panes/AppPaneMetadata'
|
||||
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
||||
|
||||
const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
||||
filesController,
|
||||
@@ -26,7 +26,7 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
||||
sortBy,
|
||||
tags,
|
||||
}) => {
|
||||
const { toggleAppPane } = useResponsiveAppPane()
|
||||
const { setPaneLayout } = useResponsiveAppPane()
|
||||
const application = useApplication()
|
||||
|
||||
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
|
||||
@@ -70,9 +70,9 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
||||
const onClick = useCallback(async () => {
|
||||
const { didSelect } = await onSelect(file, true)
|
||||
if (didSelect) {
|
||||
toggleAppPane(AppPaneId.Editor)
|
||||
setPaneLayout(PaneLayout.Editing)
|
||||
}
|
||||
}, [file, onSelect, toggleAppPane])
|
||||
}, [file, onSelect, setPaneLayout])
|
||||
|
||||
const IconComponent = () =>
|
||||
getFileIconComponent(getIconForFileType((file as FileItem).mimeType), 'w-10 h-10 flex-shrink-0')
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { ButtonStyle, getColorsForPrimaryVariant } from '../Button/Button'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
style: ButtonStyle
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const PropertiesRequiredForFixedPositioningToWorkOnIOSSafari: React.CSSProperties = {
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
}
|
||||
|
||||
const FloatingAddButton = ({ label, style, onClick }: Props) => {
|
||||
const buttonClasses = getColorsForPrimaryVariant(style)
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'fixed bottom-6 right-6 z-editor-title-bar ml-3 flex h-15 w-15 cursor-pointer items-center',
|
||||
`justify-center rounded-full border border-solid border-transparent ${buttonClasses}`,
|
||||
'hover:brightness-125',
|
||||
)}
|
||||
title={label}
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
style={PropertiesRequiredForFixedPositioningToWorkOnIOSSafari}
|
||||
>
|
||||
<Icon type="add" size="custom" className="h-8 w-8" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default FloatingAddButton
|
||||
+4
-3
@@ -86,17 +86,18 @@ const ContentListHeader = ({
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'fixed bottom-6 right-6 z-editor-title-bar ml-3 flex h-15 w-15 cursor-pointer items-center',
|
||||
'hidden md:flex',
|
||||
'h-8 w-8 hover:brightness-125',
|
||||
'z-editor-title-bar ml-3 cursor-pointer items-center',
|
||||
`justify-center rounded-full border border-solid border-transparent ${
|
||||
isDailyEntry ? 'bg-danger text-danger-contrast' : 'bg-info text-info-contrast'
|
||||
}`,
|
||||
'hover:brightness-125 md:static md:h-8 md:w-8',
|
||||
)}
|
||||
title={addButtonLabel}
|
||||
aria-label={addButtonLabel}
|
||||
onClick={addNewItem}
|
||||
>
|
||||
<Icon type="add" size="custom" className="h-8 w-8 md:h-5 md:w-5" />
|
||||
<Icon type="add" size="custom" className="h-5 w-5" />
|
||||
</button>
|
||||
)
|
||||
}, [addButtonLabel, addNewItem, isDailyEntry])
|
||||
|
||||
+4
-3
@@ -22,6 +22,7 @@ import { classNames } from '@standardnotes/utils'
|
||||
import NoSubscriptionBanner from '@/Components/NoSubscriptionBanner/NoSubscriptionBanner'
|
||||
import MenuRadioButtonItem from '@/Components/Menu/MenuRadioButtonItem'
|
||||
import MenuSwitchButtonItem from '@/Components/Menu/MenuSwitchButtonItem'
|
||||
import { Pill } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
|
||||
const DailyEntryModeEnabled = true
|
||||
|
||||
@@ -365,9 +366,9 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
<div className="flex flex-col pr-5">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="text-base font-semibold uppercase text-text lg:text-xs">Daily Notebook</div>
|
||||
<div className="ml-2 rounded bg-success px-1.5 py-[1px] text-[10px] font-bold text-success-contrast">
|
||||
Experimental
|
||||
</div>
|
||||
<Pill className="py-0 px-1.5" style="success">
|
||||
Labs
|
||||
</Pill>
|
||||
</div>
|
||||
<div className="mt-1">Capture new notes daily with a calendar-based layout</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import Portal from './Portal/Portal'
|
||||
|
||||
type FileDragTargetData = {
|
||||
tooltipText: string
|
||||
callback: (files: FileItem[]) => void
|
||||
callback: (files: FileItem) => void
|
||||
}
|
||||
|
||||
type FileDnDContextData = {
|
||||
@@ -203,14 +203,14 @@ const FileDragNDropProvider = ({ application, children, featuresController, file
|
||||
return
|
||||
}
|
||||
|
||||
const uploadedFiles = await filesController.uploadNewFile(fileOrHandle)
|
||||
const uploadedFile = await filesController.uploadNewFile(fileOrHandle)
|
||||
|
||||
if (!uploadedFiles) {
|
||||
if (!uploadedFile) {
|
||||
return
|
||||
}
|
||||
|
||||
if (closestDragTarget && dragTargets.current.has(closestDragTarget)) {
|
||||
dragTargets.current.get(closestDragTarget)?.callback(uploadedFiles)
|
||||
dragTargets.current.get(closestDragTarget)?.callback(uploadedFile)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import { getBase64FromBlob } from '@/Utils'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import Button from '../Button/Button'
|
||||
import { AppPaneId } from '../Panes/AppPaneMetadata'
|
||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||
import { createObjectURLWithRef } from './CreateObjectURLWithRef'
|
||||
import ImagePreview from './ImagePreview'
|
||||
import { ImageZoomLevelProps } from './ImageZoomLevelProps'
|
||||
@@ -26,8 +24,6 @@ const PreviewComponent: FunctionComponent<Props> = ({
|
||||
imageZoomLevel,
|
||||
setImageZoomLevel,
|
||||
}) => {
|
||||
const { selectedPane } = useResponsiveAppPane()
|
||||
|
||||
const objectUrlRef = useRef<string>()
|
||||
|
||||
const objectUrl = useMemo(() => {
|
||||
@@ -62,20 +58,14 @@ const PreviewComponent: FunctionComponent<Props> = ({
|
||||
void application.mobileDevice().previewFile(fileBase64, file.name)
|
||||
}, [application, bytes, file.mimeType, file.name, isNativeMobileWeb])
|
||||
|
||||
useEffect(() => {
|
||||
const shouldOpenNativePreviewOnLoad =
|
||||
isNativeMobileWeb && selectedPane === AppPaneId.Editor && requiresNativePreview
|
||||
if (shouldOpenNativePreviewOnLoad) {
|
||||
void openNativeFilePreview()
|
||||
}
|
||||
}, [isNativeMobileWeb, openNativeFilePreview, requiresNativePreview, selectedPane])
|
||||
|
||||
if (isNativeMobileWeb && requiresNativePreview) {
|
||||
return (
|
||||
<div className="flex flex-grow flex-col items-center justify-center">
|
||||
<div className="text-base font-bold">Previewing file...</div>
|
||||
<div className="max-w-[30ch] text-center text-base font-bold">
|
||||
This file can only be previewed in an external app
|
||||
</div>
|
||||
<Button className="mt-3" primary onClick={openNativeFilePreview}>
|
||||
Re-open file preview
|
||||
Open file preview
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -51,10 +51,8 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
if (target) {
|
||||
addDragTarget(target, {
|
||||
tooltipText: 'Drop your files to upload and link them to the current file',
|
||||
callback(files) {
|
||||
files.forEach(async (uploadedFile) => {
|
||||
await viewControllerManager.linkingController.linkItems(uploadedFile, file)
|
||||
})
|
||||
async callback(uploadedFile) {
|
||||
await viewControllerManager.linkingController.linkItems(uploadedFile, file)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import QuickSettingsButton from './QuickSettingsButton'
|
||||
import AccountMenuButton from './AccountMenuButton'
|
||||
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
||||
import UpgradeNow from './UpgradeNow'
|
||||
import PreferencesButton from './PreferencesButton'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
@@ -364,16 +365,7 @@ class Footer extends AbstractComponent<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<div className="relative z-footer-bar-item select-none">
|
||||
<StyledTooltip label="Open preferences">
|
||||
<button
|
||||
onClick={this.openPreferences}
|
||||
className="flex h-full w-8 cursor-pointer items-center justify-center"
|
||||
>
|
||||
<div className="h-5">
|
||||
<Icon type="tune" className="rounded hover:text-info" />
|
||||
</div>
|
||||
</button>
|
||||
</StyledTooltip>
|
||||
<PreferencesButton openPreferences={this.openPreferences} />
|
||||
</div>
|
||||
<div className="relative z-footer-bar-item select-none">
|
||||
<QuickSettingsButton
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { keyboardStringForShortcut, OPEN_PREFERENCES_COMMAND } from '@standardnotes/ui-services'
|
||||
import { useMemo } from 'react'
|
||||
import { useCommandService } from '../CommandProvider'
|
||||
import Icon from '../Icon/Icon'
|
||||
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
||||
|
||||
type Props = {
|
||||
openPreferences: () => void
|
||||
}
|
||||
|
||||
const PreferencesButton = ({ openPreferences }: Props) => {
|
||||
const commandService = useCommandService()
|
||||
|
||||
const shortcut = useMemo(
|
||||
() => keyboardStringForShortcut(commandService.keyboardShortcutForCommand(OPEN_PREFERENCES_COMMAND)),
|
||||
[commandService],
|
||||
)
|
||||
|
||||
return (
|
||||
<StyledTooltip label={`Open preferences (${shortcut})`}>
|
||||
<button onClick={openPreferences} className="flex h-full w-8 cursor-pointer items-center justify-center">
|
||||
<div className="h-5">
|
||||
<Icon type="tune" className="rounded hover:text-info" />
|
||||
</div>
|
||||
</button>
|
||||
</StyledTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default PreferencesButton
|
||||
@@ -1,7 +1,10 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { QuickSettingsController } from '@/Controllers/QuickSettingsController'
|
||||
import { FeatureIdentifier, SNTheme } from '@standardnotes/snjs'
|
||||
import { TOGGLE_DARK_MODE_COMMAND } from '@standardnotes/ui-services'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useCommandService } from '../CommandProvider'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Popover from '../Popover/Popover'
|
||||
import QuickSettingsMenu from '../QuickSettingsMenu/QuickSettingsMenu'
|
||||
@@ -16,6 +19,21 @@ type Props = {
|
||||
|
||||
const QuickSettingsButton = ({ application, isOpen, toggleMenu, quickSettingsMenuController }: Props) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const commandService = useCommandService()
|
||||
|
||||
useEffect(() => {
|
||||
return commandService.addCommandHandler({
|
||||
command: TOGGLE_DARK_MODE_COMMAND,
|
||||
onKeyDown: () => {
|
||||
const darkTheme = application.items
|
||||
.getDisplayableComponents()
|
||||
.find((theme) => theme.package_info.identifier === FeatureIdentifier.DarkTheme) as SNTheme | undefined
|
||||
if (darkTheme) {
|
||||
void application.mutator.toggleTheme(darkTheme)
|
||||
}
|
||||
},
|
||||
})
|
||||
}, [application, commandService])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { doesItemMatchSearchQuery } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
|
||||
import { Disclosure, DisclosurePanel } from '@reach/disclosure'
|
||||
import { classNames, ContentType, DecryptedItem, naturalSort } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, FocusEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import LinkedItemMeta from '../LinkedItems/LinkedItemMeta'
|
||||
import Menu from '../Menu/Menu'
|
||||
|
||||
type Props = {
|
||||
contentTypes: ContentType[]
|
||||
placeholder: string
|
||||
onSelection: (item: DecryptedItem) => void
|
||||
}
|
||||
|
||||
const ItemSelectionDropdown = ({ contentTypes, placeholder, onSelection }: Props) => {
|
||||
const application = useApplication()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [dropdownVisible, setDropdownVisible] = useState(false)
|
||||
|
||||
const [dropdownMaxHeight, setDropdownMaxHeight] = useState<number | 'auto'>('auto')
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const searchResultsMenuRef = useRef<HTMLMenuElement>(null)
|
||||
const [items, setItems] = useState<DecryptedItem[]>([])
|
||||
|
||||
const showDropdown = () => {
|
||||
const { clientHeight } = document.documentElement
|
||||
const inputRect = inputRef.current?.getBoundingClientRect()
|
||||
if (inputRect) {
|
||||
setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2)
|
||||
setDropdownVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
const [closeOnBlur] = useCloseOnBlur(containerRef, (visible: boolean) => {
|
||||
setDropdownVisible(visible)
|
||||
setSearchQuery('')
|
||||
})
|
||||
|
||||
const onBlur: FocusEventHandler = (event) => {
|
||||
closeOnBlur(event)
|
||||
}
|
||||
|
||||
const onSearchQueryChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
setSearchQuery(event.currentTarget.value)
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
showDropdown()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const searchableItems = naturalSort(application.items.getItems(contentTypes), 'title')
|
||||
const filteredItems = searchableItems.filter((item) => {
|
||||
return doesItemMatchSearchQuery(item, searchQuery, application)
|
||||
})
|
||||
setItems(filteredItems)
|
||||
}, [searchQuery, application, contentTypes])
|
||||
|
||||
const onSelectItem = useCallback(
|
||||
(item: DecryptedItem) => {
|
||||
onSelection(item)
|
||||
setSearchQuery('')
|
||||
setDropdownVisible(false)
|
||||
},
|
||||
[onSelection],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative" ref={containerRef}>
|
||||
<Disclosure open={dropdownVisible} onChange={showDropdown}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={classNames(
|
||||
'mr-10 w-70',
|
||||
'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs',
|
||||
'no-border h-7 focus:shadow-none focus:outline-none',
|
||||
)}
|
||||
value={searchQuery}
|
||||
onChange={onSearchQueryChange}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
onFocus={handleFocus}
|
||||
onBlur={onBlur}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
{dropdownVisible && (
|
||||
<DisclosurePanel
|
||||
className={classNames(
|
||||
'mr-10 w-70',
|
||||
'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main',
|
||||
)}
|
||||
style={{
|
||||
maxHeight: dropdownMaxHeight,
|
||||
}}
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Menu
|
||||
isOpen={dropdownVisible}
|
||||
a11yLabel="Tag search results"
|
||||
ref={searchResultsMenuRef}
|
||||
shouldAutoFocus={false}
|
||||
>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<button
|
||||
key={item.uuid}
|
||||
className={classNames(
|
||||
'flex w-full items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast',
|
||||
'hover:text-foreground focus:bg-info-backdrop',
|
||||
)}
|
||||
onClick={() => onSelectItem(item)}
|
||||
>
|
||||
<LinkedItemMeta item={item} searchQuery={searchQuery} />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
</DisclosurePanel>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(ItemSelectionDropdown)
|
||||
@@ -10,10 +10,11 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
|
||||
import { getItemTitleInContextOfLinkBubble } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
|
||||
|
||||
type Props = {
|
||||
link: ItemLink
|
||||
activateItem: (item: LinkableItem) => Promise<void>
|
||||
activateItem?: (item: LinkableItem) => Promise<void>
|
||||
unlinkItem: LinkingController['unlinkItemFromSelectedItem']
|
||||
focusPreviousItem?: () => void
|
||||
focusNextItem?: () => void
|
||||
@@ -59,7 +60,7 @@ const LinkedItemBubble = ({
|
||||
const onClick: MouseEventHandler = (event) => {
|
||||
if (wasClicked && event.target !== unlinkButtonRef.current) {
|
||||
setWasClicked(false)
|
||||
void activateItem(link.item)
|
||||
void activateItem?.(link.item)
|
||||
} else {
|
||||
setWasClicked(true)
|
||||
}
|
||||
@@ -117,7 +118,7 @@ const LinkedItemBubble = ({
|
||||
{link.type === 'linked-by' && link.item.content_type !== ContentType.Tag && (
|
||||
<span className={!isBidirectional ? 'hidden group-focus:block' : ''}>Linked By:</span>
|
||||
)}
|
||||
{link.item.title}
|
||||
{getItemTitleInContextOfLinkBubble(link.item)}
|
||||
</span>
|
||||
</span>
|
||||
{showUnlinkButton && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { getItemTitleInContextOfLinkBubble } from '@/Utils/Items/Search/doesItemMatchSearchQuery'
|
||||
|
||||
type Props = {
|
||||
item: LinkableItem
|
||||
@@ -16,7 +17,7 @@ const LinkedItemMeta = ({ item, searchQuery }: Props) => {
|
||||
const application = useApplication()
|
||||
const [icon, className] = getIconForItem(item, application)
|
||||
const tagTitle = getTitleForLinkedTag(item, application)
|
||||
const title = item.title ?? ''
|
||||
const title = getItemTitleInContextOfLinkBubble(item)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
||||
import Icon from '../Icon/Icon'
|
||||
@@ -42,7 +42,6 @@ const LinkedItemsPanel = ({
|
||||
const { entitledToFiles } = featuresController
|
||||
const application = useApplication()
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const searchInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const isSearching = !!searchQuery.length
|
||||
@@ -58,33 +57,15 @@ const LinkedItemsPanel = ({
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleFileInputChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const files = event.currentTarget.files
|
||||
|
||||
if (!files) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
void filesController.uploadNewFile(files[i]).then((uploadedFiles) => {
|
||||
if (uploadedFiles) {
|
||||
void linkItemToSelectedItem(uploadedFiles[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectAndUploadFiles = () => {
|
||||
const selectAndUploadFiles = async () => {
|
||||
if (!entitledToFiles) {
|
||||
void featuresController.showPremiumAlert(FeatureName.Files)
|
||||
return
|
||||
}
|
||||
|
||||
if (!fileInputRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
fileInputRef.current.click()
|
||||
void filesController.selectAndUploadNewFiles((file) => {
|
||||
void linkItemToSelectedItem(file)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -177,13 +158,6 @@ const LinkedItemsPanel = ({
|
||||
<div>
|
||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Linked Files</div>
|
||||
<div className="my-1">
|
||||
<input
|
||||
type="file"
|
||||
className="absolute top-0 left-0 -z-50 h-px w-px opacity-0"
|
||||
multiple
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileInputChange}
|
||||
/>
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-3 bg-transparent px-3 py-2 text-left text-base text-text hover:bg-info-backdrop hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||
onClick={selectAndUploadFiles}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CSSProperties, forwardRef, KeyboardEventHandler, ReactNode, Ref, useCallback, useEffect, useRef } from 'react'
|
||||
import { CSSProperties, forwardRef, KeyboardEventHandler, ReactNode, Ref, useCallback, useRef } from 'react'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
|
||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||
@@ -23,7 +23,6 @@ const Menu = forwardRef(
|
||||
style,
|
||||
a11yLabel,
|
||||
closeMenu,
|
||||
isOpen,
|
||||
initialFocus,
|
||||
onKeyDown,
|
||||
shouldAutoFocus = true,
|
||||
@@ -44,15 +43,7 @@ const Menu = forwardRef(
|
||||
[closeMenu, onKeyDown],
|
||||
)
|
||||
|
||||
useListKeyboardNavigation(menuElementRef, initialFocus)
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && shouldAutoFocus) {
|
||||
setTimeout(() => {
|
||||
menuElementRef.current?.focus()
|
||||
}, 5)
|
||||
}
|
||||
}, [isOpen, shouldAutoFocus])
|
||||
useListKeyboardNavigation(menuElementRef, initialFocus, shouldAutoFocus)
|
||||
|
||||
return (
|
||||
<menu
|
||||
|
||||
@@ -1,20 +1,60 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { PlatformedKeyboardShortcut } from '@standardnotes/ui-services'
|
||||
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef, ReactNode } from 'react'
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
MouseEventHandler,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||
import RadioIndicator from '../Radio/RadioIndicator'
|
||||
import MenuListItem from './MenuListItem'
|
||||
|
||||
const Tooltip = ({ text }: { text: string }) => {
|
||||
const [mobileVisible, setMobileVisible] = useState(false)
|
||||
const onClickMobile: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
setMobileVisible(!mobileVisible)
|
||||
},
|
||||
[mobileVisible],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={classNames('peer flex h-5 w-5 items-center justify-center rounded-full')} onClick={onClickMobile}>
|
||||
<Icon type={'help'} className="text-neutral" size="large" />
|
||||
<span className="sr-only">Note sync status</span>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden',
|
||||
'absolute top-full right-0 w-60 translate-x-2 translate-y-1 select-none rounded border border-border shadow-main',
|
||||
'bg-default py-1.5 px-3 text-left peer-hover:block peer-focus:block',
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
checked: boolean
|
||||
children: ReactNode
|
||||
shortcut?: PlatformedKeyboardShortcut
|
||||
info?: string
|
||||
} & ComponentPropsWithoutRef<'button'>
|
||||
|
||||
const MenuRadioButtonItem = forwardRef(
|
||||
(
|
||||
{ checked, disabled, tabIndex, children, shortcut, className, ...props }: Props,
|
||||
{ checked, disabled, tabIndex, children, shortcut, className, info, ...props }: Props,
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
return (
|
||||
@@ -37,6 +77,7 @@ const MenuRadioButtonItem = forwardRef(
|
||||
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
|
||||
<RadioIndicator disabled={disabled} checked={checked} className="flex-shrink-0" />
|
||||
{children}
|
||||
{info && <Tooltip text={info} />}
|
||||
</button>
|
||||
</MenuListItem>
|
||||
)
|
||||
|
||||
+35
-1
@@ -7,6 +7,8 @@ import {
|
||||
SNTag,
|
||||
ItemsClientInterface,
|
||||
SNNote,
|
||||
Deferred,
|
||||
SyncServiceInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { NoteViewController } from './NoteViewController'
|
||||
@@ -17,10 +19,16 @@ describe('note view controller', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
application = {} as jest.Mocked<WebApplication>
|
||||
application.streamItems = jest.fn()
|
||||
application.streamItems = jest.fn().mockReturnValue(() => {})
|
||||
application.getPreference = jest.fn().mockReturnValue(true)
|
||||
application.noAccount = jest.fn().mockReturnValue(false)
|
||||
application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
|
||||
|
||||
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemsClientInterface> })
|
||||
|
||||
Object.defineProperty(application, 'sync', { value: {} as jest.Mocked<SyncServiceInterface> })
|
||||
application.sync.sync = jest.fn().mockReturnValue(Promise.resolve())
|
||||
|
||||
componentManager = {} as jest.Mocked<SNComponentManager>
|
||||
componentManager.legacyGetDefaultEditor = jest.fn()
|
||||
Object.defineProperty(application, 'componentManager', { value: componentManager })
|
||||
@@ -80,4 +88,30 @@ describe('note view controller', () => {
|
||||
expect(controller['defaultTag']).toEqual(tag)
|
||||
expect(application.items.addTagToNote).toHaveBeenCalledWith(expect.anything(), tag, expect.anything())
|
||||
})
|
||||
|
||||
it('should wait until item finishes saving locally before deiniting', async () => {
|
||||
const note = {
|
||||
uuid: 'note-uuid',
|
||||
} as jest.Mocked<SNNote>
|
||||
|
||||
application.items.findItem = jest.fn().mockReturnValue(note)
|
||||
|
||||
const controller = new NoteViewController(application, note)
|
||||
await controller.initialize()
|
||||
|
||||
const changePromise = Deferred()
|
||||
|
||||
application.mutator.changeItem = jest.fn().mockReturnValue(changePromise.promise)
|
||||
|
||||
const savePromise = controller.saveAndAwaitLocalPropagation({ isUserModified: true, bypassDebouncer: true })
|
||||
controller.deinit()
|
||||
|
||||
expect(controller.dealloced).toEqual(false)
|
||||
|
||||
changePromise.resolve(true)
|
||||
await changePromise.promise
|
||||
await savePromise
|
||||
|
||||
expect(controller.dealloced).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
PrefKey,
|
||||
} from '@standardnotes/models'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { removeFromArray, Deferred } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemViewControllerInterface } from './ItemViewControllerInterface'
|
||||
import { TemplateNoteViewControllerOptions } from './TemplateNoteViewControllerOptions'
|
||||
@@ -29,14 +29,16 @@ const NotePreviewCharLimit = 160
|
||||
export class NoteViewController implements ItemViewControllerInterface {
|
||||
public item!: SNNote
|
||||
public dealloced = false
|
||||
public isTemplateNote = false
|
||||
public runtimeId = `${Math.random()}`
|
||||
public needsInit = true
|
||||
|
||||
private innerValueChangeObservers: ((note: SNNote, source: PayloadEmitSource) => void)[] = []
|
||||
private disposers: (() => void)[] = []
|
||||
public isTemplateNote = false
|
||||
private saveTimeout?: ReturnType<typeof setTimeout>
|
||||
private defaultTagUuid: UuidString | undefined
|
||||
private defaultTag?: SNTag
|
||||
public runtimeId = `${Math.random()}`
|
||||
public needsInit = true
|
||||
private savingLocallyPromise = Deferred<void>()
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
@@ -57,7 +59,14 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
}
|
||||
|
||||
deinit(): void {
|
||||
void this.savingLocallyPromise.promise.then(() => {
|
||||
this.performDeinitSafely()
|
||||
})
|
||||
}
|
||||
|
||||
private performDeinitSafely(): void {
|
||||
this.dealloced = true
|
||||
|
||||
for (const disposer of this.disposers) {
|
||||
disposer()
|
||||
}
|
||||
@@ -184,6 +193,8 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
throw Error('NoteViewController not initialized')
|
||||
}
|
||||
|
||||
this.savingLocallyPromise = Deferred<void>()
|
||||
|
||||
if (this.saveTimeout) {
|
||||
clearTimeout(this.saveTimeout)
|
||||
}
|
||||
@@ -198,7 +209,13 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.saveTimeout = setTimeout(() => {
|
||||
void this.undebouncedSave({ ...params, onLocalPropagationComplete: resolve })
|
||||
void this.undebouncedSave({
|
||||
...params,
|
||||
onLocalPropagationComplete: () => {
|
||||
this.savingLocallyPromise.resolve()
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
}, syncDebouceMs)
|
||||
})
|
||||
}
|
||||
@@ -262,10 +279,10 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
params.isUserModified,
|
||||
)
|
||||
|
||||
params.onLocalPropagationComplete?.()
|
||||
|
||||
void this.application.sync.sync().then(() => {
|
||||
params.onRemoteSyncComplete?.()
|
||||
})
|
||||
|
||||
params.onLocalPropagationComplete?.()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,9 +905,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{editorMode !== 'super' && (
|
||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||
)}
|
||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ const NoteViewFileDropTarget = ({ note, linkingController, noteViewElement, file
|
||||
if (target) {
|
||||
addDragTarget(target, {
|
||||
tooltipText: 'Drop your files to upload and link them to the current note',
|
||||
callback: (files) => {
|
||||
files.forEach(async (uploadedFile) => {
|
||||
await linkingController.linkItems(note, uploadedFile)
|
||||
filesController.notifyObserversOfUploadedFileLinkingToCurrentNote(uploadedFile.uuid)
|
||||
})
|
||||
callback: async (uploadedFile) => {
|
||||
await linkingController.linkItems(note, uploadedFile)
|
||||
filesController.notifyObserversOfUploadedFileLinkingToCurrentNote(uploadedFile.uuid)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user