Compare commits

...
Author SHA1 Message Date
StandardNotes CI 8755001f7e chore(release): publish
- @standardnotes/[email protected].9
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-11-23 02:16:46 +00:00
Mo 7c2e832065 feat: display file backup status in file context menu (#2044) (skip e2e)
* feat: show file backup status in context menu

* feat: show backup status in list cell

* feat: mapping cache

* feat: add to linking menu + date format

* fix: types
2022-11-22 19:58:48 -06:00
Aman Harwara 096d82f7af fix: lazy load embedded files in super editor (#2043) 2022-11-23 00:55:20 +05:30
Aman Harwara 8c8f045b9a fix: super editor popover menus (#2041) 2022-11-22 19:16:59 +05:30
StandardNotes CI b3140f1623 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].1
2022-11-22 10:15:26 +00:00
Karol Sójko c5e104f90b fix(snjs): add handling errors on creating websocket connection 2022-11-22 10:47:54 +01:00
StandardNotes CI 09b8f00eed chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].0
2022-11-19 15:17:36 +00:00
Mo c39c72da7a feat: ability to drag super list items; secure password generation blocks (#2039)
* feat: ability to drag list item nodes

* fix: issue where editor focus would scroll to bottom

* fix: improve drag icon and prevent from interfering with selection

* fix(super): add 'current' as keyword for bringing up date block options

* fix(super): issue with autocomplete menu width on large screens

* feat(super): ability to generate secure random passwords
2022-11-19 08:53:30 -06:00
84 changed files with 720 additions and 194 deletions
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/api
## [1.20.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.20.8",
"version": "1.20.9",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.7.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
### Features
* ability to drag super list items; secure password generation blocks ([#2039](https://github.com/standardnotes/app/issues/2039)) ([c39c72d](https://github.com/standardnotes/app/commit/c39c72da7a4fb85f4da9aa4e6f8e9f7ba4486a94))
## [1.6.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/blocks-editor",
"version": "1.6.2",
"version": "1.7.0",
"private": true,
"main": "./src/index.ts",
"scripts": {
@@ -98,7 +98,7 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
<div className="editor" ref={onRef}>
<ContentEditable
id={SuperEditorContentId}
className={`ContentEditable__root ${className}`}
className={`ContentEditable__root overflow-y-auto ${className}`}
spellCheck={spellcheck}
/>
</div>
@@ -1,18 +1,21 @@
.draggable-block-menu {
border-radius: 4px;
padding: 2px 1px;
padding: 3px 1px;
cursor: grab;
opacity: 0;
position: absolute;
left: 0;
top: 0;
will-change: transform;
transition: opacity 0.3s;
}
.draggable-block-menu .icon {
width: 1rem;
height: 1rem;
opacity: 0.4;
width: 0.8rem;
height: 1.1rem;
opacity: 0.2;
padding-left: 4.75px;
padding-top: 2px;
}
.draggable-block-menu:active {
@@ -21,7 +24,6 @@
.draggable-block-menu:hover {
background-color: var(--sn-stylekit-contrast-background-color);
padding: 3px;
}
.draggable-block-target-line {
@@ -32,5 +34,6 @@
left: 0;
top: 0;
opacity: 0;
will-change: transform;
will-change: transform, opacity;
transition: opacity 0.15s;
}
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
*/
import {$createListNode, $isListNode} from '@lexical/list';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {eventFiles} from '@lexical/rich-text';
import {mergeRegister} from '@lexical/utils';
@@ -19,17 +20,17 @@ import {
} from 'lexical';
import {DragEvent as ReactDragEvent, useEffect, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
import {LexicalDraggableBlockMenu} from '@standardnotes/icons';
import {BlockIcon} from '@standardnotes/icons';
import {isHTMLElement} from '../../Utils/guard';
import {Point} from '../../Utils/point';
import {Rect} from '../../Utils/rect';
import {ContainsPointReturn, Rect} from '../../Utils/rect';
const SPACE = 4;
const TARGET_LINE_HALF_HEIGHT = 2;
const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
const TEXT_BOX_HORIZONTAL_PADDING = 28;
const TEXT_BOX_HORIZONTAL_PADDING = 24;
const Downward = 1;
const Upward = -1;
@@ -53,12 +54,56 @@ function getTopLevelNodeKeys(editor: LexicalEditor): string[] {
return root ? root.__children : [];
}
function elementContainingEventLocation(
anchorElem: HTMLElement,
element: HTMLElement,
event: MouseEvent,
): {contains: ContainsPointReturn; element: HTMLElement} {
const anchorElementRect = anchorElem.getBoundingClientRect();
const eventLocation = new Point(event.x, event.y);
const elementDomRect = Rect.fromDOM(element);
const {marginTop, marginBottom} = window.getComputedStyle(element);
const rect = elementDomRect.generateNewRect({
bottom: elementDomRect.bottom + parseFloat(marginBottom),
left: anchorElementRect.left,
right: anchorElementRect.right,
top: elementDomRect.top - parseFloat(marginTop),
});
const children = Array.from(element.children);
const shouldRecurseIntoChildren = ['UL', 'OL', 'LI'].includes(
element.tagName,
);
if (shouldRecurseIntoChildren) {
for (const child of children) {
const isLeaf = child.children.length === 0;
if (isLeaf) {
continue;
}
const childResult = elementContainingEventLocation(
anchorElem,
child as HTMLElement,
event,
);
if (childResult.contains.result) {
return childResult;
}
}
}
return {contains: rect.contains(eventLocation), element: element};
}
function getBlockElement(
anchorElem: HTMLElement,
editor: LexicalEditor,
event: MouseEvent,
): HTMLElement | null {
const anchorElementRect = anchorElem.getBoundingClientRect();
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
let blockElem: HTMLElement | null = null;
@@ -73,32 +118,22 @@ function getBlockElement(
if (elem === null) {
break;
}
const point = new Point(event.x, event.y);
const domRect = Rect.fromDOM(elem);
const {marginTop, marginBottom} = window.getComputedStyle(elem);
const {contains, element} = elementContainingEventLocation(
anchorElem,
elem,
event,
);
const rect = domRect.generateNewRect({
bottom: domRect.bottom + parseFloat(marginBottom),
left: anchorElementRect.left,
right: anchorElementRect.right,
top: domRect.top - parseFloat(marginTop),
});
const {
result,
reason: {isOnTopSide, isOnBottomSide},
} = rect.contains(point);
if (result) {
blockElem = elem;
if (contains.result) {
blockElem = element;
prevIndex = index;
break;
}
if (direction === Indeterminate) {
if (isOnTopSide) {
if (contains.reason.isOnTopSide) {
direction = Upward;
} else if (isOnBottomSide) {
} else if (contains.reason.isOnBottomSide) {
direction = Downward;
} else {
// stop search block element
@@ -124,7 +159,6 @@ function setMenuPosition(
) {
if (!targetElem) {
floatingElem.style.opacity = '0';
floatingElem.style.transform = 'translate(-10000px, -10000px)';
return;
}
@@ -186,13 +220,12 @@ function setTargetLine(
targetLineElem.style.width = `${
anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2
}px`;
targetLineElem.style.opacity = '.4';
targetLineElem.style.opacity = '.6';
}
function hideTargetLine(targetLineElem: HTMLElement | null) {
if (targetLineElem) {
targetLineElem.style.opacity = '0';
targetLineElem.style.transform = 'translate(-10000px, -10000px)';
}
}
@@ -284,18 +317,30 @@ function useDraggableBlockMenu(
return false;
}
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
if (!targetNode) {
return false;
}
if (targetNode === draggedNode) {
return true;
}
let nodeToInsert = draggedNode;
const targetParent = targetNode.getParent();
const sourceParent = draggedNode.getParent();
if ($isListNode(sourceParent) && !$isListNode(targetParent)) {
const newList = $createListNode(sourceParent.getListType());
newList.append(draggedNode);
nodeToInsert = newList;
}
const {top, height} = targetBlockElem.getBoundingClientRect();
const shouldInsertAfter = pageY - top > height / 2;
if (shouldInsertAfter) {
targetNode.insertAfter(draggedNode);
targetNode.insertAfter(nodeToInsert);
} else {
targetNode.insertBefore(draggedNode);
targetNode.insertBefore(nodeToInsert);
}
setDraggableBlockElem(null);
@@ -349,7 +394,7 @@ function useDraggableBlockMenu(
onDragStart={onDragStart}
onDragEnd={onDragEnd}>
<div className={isEditable ? 'icon' : ''}>
<LexicalDraggableBlockMenu className="text-text pointer-events-none" />
<BlockIcon className="text-text pointer-events-none" />
</div>
</div>
<div className="draggable-block-target-line" ref={targetLineRef} />
@@ -110,8 +110,8 @@ export function InsertTableDialog({
return (
<>
<TextInput label="No of rows" onChange={setRows} value={rows} />
<TextInput label="No of columns" onChange={setColumns} value={columns} />
<TextInput label="Number of rows" onChange={setRows} value={rows} />
<TextInput label="Number of columns" onChange={setColumns} value={columns} />
<DialogActions data-test-id="table-model-confirm-insert">
<Button onClick={onClick}>Confirm</Button>
</DialogActions>
@@ -13,13 +13,13 @@
padding-left: 15px;
padding-right: 15px;
border: 0px;
background-color: #eee;
border-radius: 5px;
background-color: var(--sn-stylekit-contrast-background-color);
cursor: pointer;
font-size: 14px;
}
.Button__root:hover {
background-color: #ddd;
background-color: var(--sn-stylekit-info-color);
color: var(--sn-stylekit-info-contrast-color);
}
.Button__small {
padding-top: 5px;
@@ -32,5 +32,5 @@
cursor: not-allowed;
}
.Button__disabled:hover {
background-color: #eee;
background-color: var(--sn-stylekit-secondary-background-color);
}
@@ -17,16 +17,17 @@
display: flex;
flex: 1;
color: #666;
margin-right: 20px;
}
.Input__input {
display: flex;
flex: 2;
border: 1px solid #999;
border: 1px solid var(--sn-stylekit-contrast-border-color);
background-color: var(--sn-stylekit-contrast-background-color);
padding-top: 7px;
padding-bottom: 7px;
padding-left: 10px;
padding-right: 10px;
font-size: 16px;
border-radius: 5px;
min-width: 0;
}
@@ -17,7 +17,7 @@
bottom: 0px;
left: 0px;
right: 0px;
background-color: rgba(40, 40, 40, 0.6);
background-color: rgba(0, 0, 0, 0.7);
flex-grow: 0px;
flex-shrink: 1px;
z-index: 100;
@@ -28,22 +28,23 @@
min-width: 300px;
display: flex;
flex-grow: 0px;
background-color: #fff;
background-color: var(--sn-stylekit-background-color);
flex-direction: column;
position: relative;
box-shadow: 0 0 20px 0 #444;
border-radius: 10px;
box-shadow: 0 0px 0 var(--sn-stylekit-shadow-color);
border-radius: 0px;
}
.Modal__title {
color: #444;
color:var(--sn-stylekit-foreground-color);
margin: 0px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
padding-bottom: 15px;
border-bottom: 1px solid var(--sn-stylekit-border-color);
}
.Modal__closeButton {
border: 0px;
position: absolute;
right: 20px;
top: 15px;
border-radius: 20px;
justify-content: center;
align-items: center;
@@ -52,10 +53,11 @@
height: 30px;
text-align: center;
cursor: pointer;
background-color: #eee;
background-color: var(--sn-stylekit-contrast-background-color);
}
.Modal__closeButton:hover {
background-color: #ddd;
background-color: var(--sn-stylekit-info-color);
color: var(--sn-stylekit-info-contrast-color);
}
.Modal__content {
padding-top: 20px;
@@ -73,7 +73,7 @@ function PortalImpl({
aria-label="Close modal"
type="button"
onClick={onClose}>
X
</button>
<div className="Modal__content">{children}</div>
</div>
+14
View File
@@ -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.
# [3.101.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [3.100.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
**Note:** Version bump only for package @standardnotes/desktop
## [3.100.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.100.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/desktop
@@ -1,4 +1,4 @@
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
import { FileBackupRecord, FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
import { AppState } from 'app/AppState'
import { shell } from 'electron'
import { StoreKeys } from '../Store/StoreKeys'
@@ -120,6 +120,10 @@ export class FilesBackupManager implements FileBackupsDevice {
return this.defaultMappingFileValue()
}
for (const entry of Object.values(data.files)) {
entry.backedUpOn = new Date(entry.backedUpOn)
}
return data
}
@@ -129,6 +133,10 @@ export class FilesBackupManager implements FileBackupsDevice {
void shell.openPath(location)
}
async openFileBackup(record: FileBackupRecord): Promise<void> {
void shell.openPath(record.absolutePath)
}
async saveFilesBackupsMappingFile(file: FileBackupsMapping): Promise<'success' | 'failed'> {
await writeJSONFile(this.getMappingFileLocation(), file)
@@ -5,7 +5,7 @@ import { StoreKeys } from '../Store/StoreKeys'
const path = require('path')
const rendererPath = path.join('file://', __dirname, '/renderer.js')
import { FileBackupsDevice, FileBackupsMapping } from '@web/Application/Device/DesktopSnjsExports'
import { FileBackupsDevice, FileBackupsMapping, FileBackupRecord } from '@web/Application/Device/DesktopSnjsExports'
import { app, BrowserWindow } from 'electron'
import { BackupsManagerInterface } from '../Backups/BackupsManagerInterface'
import { KeychainInterface } from '../Keychain/KeychainInterface'
@@ -64,6 +64,7 @@ export class RemoteBridge implements CrossProcessBridge {
changeFilesBackupsLocation: this.changeFilesBackupsLocation.bind(this),
getFilesBackupsLocation: this.getFilesBackupsLocation.bind(this),
openFilesBackupsLocation: this.openFilesBackupsLocation.bind(this),
openFileBackup: this.openFileBackup.bind(this),
}
}
@@ -202,4 +203,8 @@ export class RemoteBridge implements CrossProcessBridge {
public openFilesBackupsLocation(): Promise<void> {
return this.fileBackups.openFilesBackupsLocation()
}
public openFileBackup(record: FileBackupRecord): Promise<void> {
return this.fileBackups.openFileBackup(record)
}
}
@@ -3,6 +3,7 @@ import {
Environment,
FileBackupsMapping,
RawKeychainValue,
FileBackupRecord,
} from '@web/Application/Device/DesktopSnjsExports'
import { WebOrDesktopDevice } from '@web/Application/Device/WebOrDesktopDevice'
import { Component } from '../Main/Packages/PackageManagerInterface'
@@ -132,6 +133,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
return this.remoteBridge.openFilesBackupsLocation()
}
openFileBackup(record: FileBackupRecord): Promise<void> {
return this.remoteBridge.openFileBackup(record)
}
async saveFilesBackupsFile(
uuid: string,
metaFile: string,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.100.16",
"version": "3.101.0",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/encryption
## [1.19.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.19.16",
"version": "1.19.17",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [1.25.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/filepicker
+4 -11
View File
@@ -1,24 +1,17 @@
{
"name": "@standardnotes/filepicker",
"version": "1.25.16",
"version": "1.26.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
"description": "Web filepicker for Standard Notes projects",
"main": "dist/index.js",
"main": "./src/index.ts",
"author": "Standard Notes",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"types": "./src/index.ts",
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
"start": "tsc -p tsconfig.json --watch",
"prebuild": "yarn clean",
"build": "tsc -p tsconfig.json",
"build": "echo 'Empty build script required for yarn topological install'",
"lint": "eslint src --ext .ts",
"test": "jest"
},
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
"extends": "../../UILib.tsconfig.json",
"compilerOptions": {
"skipLibCheck": true,
"rootDir": "./src",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [1.12.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/files
+5 -9
View File
@@ -1,26 +1,22 @@
{
"name": "@standardnotes/files",
"version": "1.12.16",
"version": "1.13.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
"description": "Client-side files library",
"main": "dist/index.js",
"main": "./src/index.ts",
"author": "Standard Notes",
"types": "dist/index.d.ts",
"types": "./src/index.ts",
"files": [
"dist"
],
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
"start": "tsc -p tsconfig.json --watch",
"prebuild": "yarn clean",
"build": "tsc -p tsconfig.json",
"lint": "eslint src --ext .ts",
"test": "jest"
"test": "jest",
"build": "echo 'Empty build script required for yarn topological install'"
},
"devDependencies": {
"@types/jest": "^29.2.3",
@@ -1,5 +1,5 @@
import { Uuid } from '@standardnotes/common'
import { FileBackupsMapping } from './FileBackupsMapping'
import { FileBackupRecord, FileBackupsMapping } from './FileBackupsMapping'
export interface FileBackupsDevice {
getFilesBackupsMappingFile(): Promise<FileBackupsMapping>
@@ -18,4 +18,5 @@ export interface FileBackupsDevice {
changeFilesBackupsLocation(): Promise<string | undefined>
getFilesBackupsLocation(): Promise<string>
openFilesBackupsLocation(): Promise<void>
openFileBackup(record: FileBackupRecord): Promise<void>
}
@@ -1,17 +1,16 @@
import { Uuid } from '@standardnotes/common'
import { FileBackupsConstantsV1 } from './FileBackupsConstantsV1'
export type FileBackupRecord = {
backedUpOn: Date
absolutePath: string
relativePath: string
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
version: typeof FileBackupsConstantsV1.Version
}
export interface FileBackupsMapping {
version: typeof FileBackupsConstantsV1.Version
files: Record<
Uuid,
{
backedUpOn: Date
absolutePath: string
relativePath: string
metadataFileName: typeof FileBackupsConstantsV1.MetadataFileName
binaryFileName: typeof FileBackupsConstantsV1.BinaryFileName
version: typeof FileBackupsConstantsV1.Version
}
>
files: Record<Uuid, FileBackupRecord>
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
"extends": "../../UILib.tsconfig.json",
"compilerOptions": {
"skipLibCheck": true,
"rootDir": "./src",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
### Features
* ability to drag super list items; secure password generation blocks ([#2039](https://github.com/standardnotes/app/issues/2039)) ([c39c72d](https://github.com/standardnotes/app/commit/c39c72da7a4fb85f4da9aa4e6f8e9f7ba4486a94))
# [1.5.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/icons",
"version": "1.5.0",
"version": "1.6.0",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
+7
View File
@@ -0,0 +1,7 @@
<svg width="9px" height="14px" viewBox="0 0 9 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-2" fill="currentColor" fill-rule="nonzero">
<path d="M1.6666,3.33333 C1.2246,3.33333 0.8007,3.15774 0.4881,2.84518 C0.1755,2.53262 0,2.1087 0,1.66667 C0,1.22464 0.1755,0.80072 0.4881,0.48816 C0.8007,0.1756 1.2246,0 1.6666,0 C2.1086,0 2.5326,0.1756 2.8451,0.48816 C3.1577,0.80072 3.3333,1.22464 3.3333,1.66667 C3.3333,2.1087 3.1577,2.53262 2.8451,2.84518 C2.5326,3.15774 2.1086,3.33333 1.6666,3.33333 Z M1.6666,8.33333 C1.2246,8.33333 0.8007,8.15773 0.4881,7.84513 C0.1755,7.53263 0,7.10873 0,6.66663 C0,6.22463 0.1755,5.80073 0.4881,5.48813 C0.8007,5.17563 1.2246,5.00003 1.6666,5.00003 C2.1086,5.00003 2.5326,5.17563 2.8451,5.48813 C3.1577,5.80073 3.3333,6.22463 3.3333,6.66663 C3.3333,7.10873 3.1577,7.53263 2.8451,7.84513 C2.5326,8.15773 2.1086,8.33333 1.6666,8.33333 Z M1.6666,13.33333 C1.2246,13.33333 0.8007,13.15773 0.4881,12.84513 C0.1755,12.53263 0,12.10873 0,11.66663 C0,11.22463 0.1755,10.80073 0.4881,10.48813 C0.8007,10.17563 1.2246,10.00003 1.6666,10.00003 C2.1086,10.00003 2.5326,10.17563 2.8451,10.48813 C3.1577,10.80073 3.3333,11.22463 3.3333,11.66663 C3.3333,12.10873 3.1577,12.53263 2.8451,12.84513 C2.5326,13.15773 2.1086,13.33333 1.6666,13.33333 L1.6666,13.33333 Z M6.6666,3.33333 C6.2246,3.33333 5.8007,3.15774 5.4881,2.84518 C5.1755,2.53262 5,2.1087 5,1.66667 C5,1.22464 5.1755,0.80072 5.4881,0.48816 C5.8007,0.1756 6.2246,0 6.6666,0 C7.1086,0 7.5326,0.1756 7.8451,0.48816 C8.1577,0.80072 8.3333,1.22464 8.3333,1.66667 C8.3333,2.1087 8.1577,2.53262 7.8451,2.84518 C7.5326,3.15774 7.1086,3.33333 6.6666,3.33333 Z M6.6666,8.33333 C6.2246,8.33333 5.8007,8.15773 5.4881,7.84513 C5.1755,7.53263 5,7.10873 5,6.66663 C5,6.22463 5.1755,5.80073 5.4881,5.48813 C5.8007,5.17563 6.2246,5.00003 6.6666,5.00003 C7.1086,5.00003 7.5326,5.17563 7.8451,5.48813 C8.1577,5.80073 8.3333,6.22463 8.3333,6.66663 C8.3333,7.10873 8.1577,7.53263 7.8451,7.84513 C7.5326,8.15773 7.1086,8.33333 6.6666,8.33333 Z M6.6666,13.33333 C6.2246,13.33333 5.8007,13.15773 5.4881,12.84513 C5.1755,12.53263 5,12.10873 5,11.66663 C5,11.22463 5.1755,10.80073 5.4881,10.48813 C5.8007,10.17563 6.2246,10.00003 6.6666,10.00003 C7.1086,10.00003 7.5326,10.17563 7.8451,10.48813 C8.1577,10.80073 8.3333,11.22463 8.3333,11.66663 C8.3333,12.10873 8.1577,12.53263 7.8451,12.84513 C7.5326,13.15773 7.1086,13.33333 6.6666,13.33333 L6.6666,13.33333 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+2
View File
@@ -21,6 +21,7 @@ import AttachmentFileIcon from './ic-attachment-file.svg'
import AuthenticatorIcon from './ic-authenticator.svg'
import AuthenticatorVariantIcon from './ic-authenticator-variant.svg'
import BackIosIcon from './ic-back-ios.svg'
import BlockIcon from './ic-block.svg'
import BlueDotIcon from './blue-dot.svg'
import BoldIcon from './ic-bold.svg'
import BoxFilledIcon from './ic-box-filled.svg'
@@ -224,6 +225,7 @@ export {
AuthenticatorIcon,
AuthenticatorVariantIcon,
BackIosIcon,
BlockIcon,
BlueDotIcon,
BoldIcon,
BoxFilledIcon,
+12
View File
@@ -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.
## [3.46.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/mobile
## [3.46.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
**Note:** Version bump only for package @standardnotes/mobile
## [3.46.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.46.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.46.26",
"version": "3.46.29",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/models
## [1.37.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.37.5",
"version": "1.37.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+12
View File
@@ -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.3.255](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.254](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.253](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/releases
## [1.3.252](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.3.252",
"version": "1.3.255",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.46.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [1.45.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.45.6",
"version": "1.46.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,7 +2,13 @@ import { ContentType, Uuid } from '@standardnotes/common'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { PayloadEmitSource, FileItem, CreateEncryptedBackupFileContextPayload } from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { FilesApiInterface, FileBackupMetadataFile, FileBackupsDevice, FileBackupsMapping } from '@standardnotes/files'
import {
FilesApiInterface,
FileBackupMetadataFile,
FileBackupsDevice,
FileBackupsMapping,
FileBackupRecord,
} from '@standardnotes/files'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { AbstractService } from '../Service/AbstractService'
@@ -11,6 +17,7 @@ import { StatusServiceInterface } from '../Status/StatusServiceInterface'
export class FilesBackupService extends AbstractService {
private itemsObserverDisposer: () => void
private pendingFiles = new Set<Uuid>()
private mappingCache?: FileBackupsMapping['files']
constructor(
private items: ItemManagerInterface,
@@ -75,8 +82,30 @@ export class FilesBackupService extends AbstractService {
return this.device.openFilesBackupsLocation()
}
private async getBackupsMapping(): Promise<FileBackupsMapping['files']> {
return (await this.device.getFilesBackupsMappingFile()).files
private async getBackupsMappingFromDisk(): Promise<FileBackupsMapping['files']> {
const result = (await this.device.getFilesBackupsMappingFile()).files
this.mappingCache = result
return result
}
private invalidateMappingCache(): void {
this.mappingCache = undefined
}
private async getBackupsMappingFromCache(): Promise<FileBackupsMapping['files']> {
return this.mappingCache ?? (await this.getBackupsMappingFromDisk())
}
public async getFileBackupInfo(file: FileItem): Promise<FileBackupRecord | undefined> {
const mapping = await this.getBackupsMappingFromCache()
const record = mapping[file.uuid]
return record
}
public async openFileBackup(record: FileBackupRecord): Promise<void> {
await this.device.openFileBackup(record)
}
private async handleChangedFiles(files: FileItem[]): Promise<void> {
@@ -88,7 +117,7 @@ export class FilesBackupService extends AbstractService {
return
}
const mapping = await this.getBackupsMapping()
const mapping = await this.getBackupsMappingFromDisk()
for (const file of files) {
if (this.pendingFiles.has(file.uuid)) {
@@ -105,6 +134,8 @@ export class FilesBackupService extends AbstractService {
this.pendingFiles.delete(file.uuid)
}
}
this.invalidateMappingCache()
}
private async performBackupOperation(file: FileItem): Promise<'success' | 'failed' | 'aborted'> {
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.151.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/snjs
## [2.151.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
### Bug Fixes
* **snjs:** add handling errors on creating websocket connection ([c5e104f](https://github.com/standardnotes/app/commit/c5e104f90bcd0d494b9d4adc0b27f2f942dfe71c))
## [2.151.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/snjs
@@ -70,6 +70,11 @@ export class SNWebSocketsService extends AbstractService<WebSocketsServiceEvent,
private async createWebSocketConnectionToken(): Promise<string | undefined> {
try {
const response = await this.webSocketApiService.createConnectionToken()
if (response.data.error) {
console.error(response.data.error)
return undefined
}
return response.data.token
} catch (error) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.151.7",
"version": "2.151.9",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/toast
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/toast",
"version": "1.3.10",
"version": "1.3.11",
"private": true,
"main": "./src/index.ts",
"scripts": {
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.14.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.14.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.14.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
**Note:** Version bump only for package @standardnotes/ui-services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.14.1",
"version": "1.14.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.12.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [1.11.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
**Note:** Version bump only for package @standardnotes/utils
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/utils",
"version": "1.11.3",
"version": "1.12.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -41,6 +41,7 @@
"eslint-plugin-prettier": "*",
"jest": "^29.3.1",
"jsdom": "^20.0.2",
"ts-jest": "^29.0.3"
"ts-jest": "^29.0.3",
"typescript": "*"
}
}
+21
View File
@@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.105.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-23)
### Bug Fixes
* lazy load embedded files in super editor ([#2043](https://github.com/standardnotes/app/issues/2043)) ([096d82f](https://github.com/standardnotes/app/commit/096d82f7af73613a3d8bc994bec2ec10a92a527c))
* super editor popover menus ([#2041](https://github.com/standardnotes/app/issues/2041)) ([8c8f045](https://github.com/standardnotes/app/commit/8c8f045b9a53d910bcbf1dc5ee19cf92addf252e))
### Features
* display file backup status in file context menu ([#2044](https://github.com/standardnotes/app/issues/2044)) (skip e2e) ([7c2e832](https://github.com/standardnotes/app/commit/7c2e832065d45676f1b69b7c386e789e2f76775e))
## [3.104.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-22)
**Note:** Version bump only for package @standardnotes/web
# [3.104.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
### Features
* ability to drag super list items; secure password generation blocks ([#2039](https://github.com/standardnotes/app/issues/2039)) ([c39c72d](https://github.com/standardnotes/app/commit/c39c72da7a4fb85f4da9aa4e6f8e9f7ba4486a94))
## [3.103.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-19)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.103.2",
"version": "3.105.0",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
@@ -30,6 +30,7 @@ import {
ArchiveManager,
AutolockService,
KeyboardService,
PreferenceId,
RouteService,
RouteServiceInterface,
ThemeManager,
@@ -387,4 +388,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter
FeatureIdentifier.PlainEditor
)
}
openPreferences(pane?: PreferenceId): void {
this.getViewControllerManager().preferencesController.openPreferences()
if (pane) {
this.getViewControllerManager().preferencesController.setCurrentPane(pane)
}
}
}
@@ -6,4 +6,5 @@ export {
DesktopClientRequiresWebMethods,
FileBackupsMapping,
FileBackupsDevice,
FileBackupRecord,
} from '@standardnotes/snjs'
@@ -1,6 +1,6 @@
import { FileItem } from '@standardnotes/snjs'
import { FileItem, FileBackupRecord } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef } from 'react'
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
import { getFileIconComponent } from '../FilePreview/getFileIconComponent'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemTags from './ListItemTags'
@@ -12,19 +12,28 @@ import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import Icon from '../Icon/Icon'
const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
filesController,
hideDate,
hideIcon,
hideTags,
item,
item: file,
onSelect,
selected,
sortBy,
tags,
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const application = useApplication()
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
useEffect(() => {
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo)
}, [application, file])
const listItemRef = useRef<HTMLDivElement>(null)
@@ -45,7 +54,7 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
let shouldOpenContextMenu = selected
if (!selected) {
const { didSelect } = await onSelect(item)
const { didSelect } = await onSelect(file)
if (didSelect) {
shouldOpenContextMenu = true
}
@@ -55,18 +64,18 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
openFileContextMenu(posX, posY)
}
},
[selected, onSelect, item, openFileContextMenu],
[selected, onSelect, file, openFileContextMenu],
)
const onClick = useCallback(async () => {
const { didSelect } = await onSelect(item, true)
const { didSelect } = await onSelect(file, true)
if (didSelect) {
toggleAppPane(AppPaneId.Editor)
}
}, [item, onSelect, toggleAppPane])
}, [file, onSelect, toggleAppPane])
const IconComponent = () =>
getFileIconComponent(getIconForFileType((item as FileItem).mimeType), 'w-10 h-10 flex-shrink-0')
getFileIconComponent(getIconForFileType((file as FileItem).mimeType), 'w-10 h-10 flex-shrink-0')
useContextMenuEvent(listItemRef, openContextMenu)
@@ -74,7 +83,7 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
<div
ref={listItemRef}
className={classNames('flex max-h-[300px] w-[190px] cursor-pointer px-1 pt-2 text-text md:w-[200px]')}
id={item.uuid}
id={file.uuid}
onClick={onClick}
>
<div
@@ -92,11 +101,11 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
)}
<div className="min-w-0 flex-grow py-4 px-0">
<div className="line-clamp-2 overflow-hidden text-editor font-semibold">
<div className="break-word line-clamp-2 mr-2 overflow-hidden">{item.title}</div>
<div className="break-word line-clamp-2 mr-2 overflow-hidden">{file.title}</div>
</div>
<ListItemMetadata item={item} hideDate={hideDate} sortBy={sortBy} />
<ListItemMetadata item={file} hideDate={hideDate} sortBy={sortBy} />
<ListItemTags hideTags={hideTags} tags={tags} />
<ListItemConflictIndicator item={item} />
<ListItemConflictIndicator item={file} />
</div>
</div>
<div
@@ -105,7 +114,14 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
selected ? 'bg-info text-info-contrast' : 'bg-passive-4 text-neutral',
)}
>
{formatSizeToReadableString(item.decryptedSize)}
<div className="flex justify-between">
{formatSizeToReadableString(file.decryptedSize)}
{backupInfo && (
<div title="File is backed up locally">
<Icon type="check-circle" />
</div>
)}
</div>
</div>
</div>
</div>
@@ -20,7 +20,6 @@ const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, s
open={showFileContextMenu}
anchorPoint={fileContextMenuLocation}
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
side="right"
align="start"
className="py-2"
>
@@ -0,0 +1,57 @@
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import MenuItem from '../Menu/MenuItem'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { FileBackupRecord, FileItem } from '@standardnotes/snjs'
import { dateToStringStyle1 } from '@/Utils/DateUtils'
export const FileContextMenuBackupOption: FunctionComponent<{ file: FileItem }> = ({ file }) => {
const application = useApplication()
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
useEffect(() => {
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo)
}, [application, file])
const openFileBackup = useCallback(() => {
if (backupInfo) {
void application.fileBackups?.openFileBackup(backupInfo)
}
}, [backupInfo, application])
const configureFileBackups = useCallback(() => {
application.openPreferences('backups')
}, [application])
return (
<>
{backupInfo && (
<MenuItem
icon={'check-circle'}
iconClassName={'text-success mt-1'}
className={'items-start'}
onClick={openFileBackup}
>
<div className="ml-2">
<div className="font-semibold text-success">Backed up on {dateToStringStyle1(backupInfo.backedUpOn)}</div>
<div className="text-xs text-neutral">{backupInfo.absolutePath}</div>
</div>
</MenuItem>
)}
{!backupInfo && application.fileBackups && (
<MenuItem
icon={'safe-square'}
className={'items-start'}
iconClassName={'text-neutral mt-1'}
onClick={configureFileBackups}
>
<div className="ml-2">
<div>Configure file backups</div>
<div className="text-xs text-neutral">File not backed up locally</div>
</div>
</MenuItem>
)}
</>
)
}
@@ -10,6 +10,7 @@ import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import MenuItem from '../Menu/MenuItem'
import { MenuItemType } from '../Menu/MenuItemType'
import { FileContextMenuBackupOption } from './FileContextMenuBackupOption'
type Props = {
closeMenu: () => void
@@ -120,6 +121,9 @@ const FileMenuOptions: FunctionComponent<Props> = ({
<Icon type="trash" className="mr-2 text-danger" />
<span className="text-danger">Delete permanently</span>
</MenuItem>
<FileContextMenuBackupOption file={selectedFiles[0]} />
<HorizontalSeparator classes="my-2" />
<div className="px-3 pt-1 pb-0.5 text-xs font-medium text-neutral">
{!hasSelectedMultipleFiles && (
@@ -6,7 +6,8 @@ import Spinner from '@/Components/Spinner/Spinner'
import FilePreviewError from './FilePreviewError'
import { isFileTypePreviewable } from './isFilePreviewable'
import PreviewComponent from './PreviewComponent'
import ProtectedItemOverlay from '../ProtectedItemOverlay/ProtectedItemOverlay'
import Button from '../Button/Button'
import { ProtectedIllustration } from '@standardnotes/icons'
type Props = {
application: WebApplication
@@ -77,13 +78,28 @@ const FilePreview = ({ file, application }: Props) => {
}, [application.files, downloadedBytes, file, isFilePreviewable, isAuthorized])
if (!isAuthorized) {
const hasProtectionSources = application.hasProtectionSources()
return (
<ProtectedItemOverlay
showAccountMenu={application.showAccountMenu}
itemType={'file'}
onViewItem={() => application.protections.authorizeItemAccess(file)}
hasProtectionSources={application.hasProtectionSources()}
/>
<div className="flex flex-grow flex-col items-center justify-center">
<ProtectedIllustration className="mb-4 h-30 w-30" />
<div className="mb-2 text-base font-bold">This file is protected.</div>
<p className="max-w-[35ch] text-center text-sm text-passive-0">
{hasProtectionSources
? 'Authenticate to view this file.'
: 'Add a passcode or create an account to require authentication to view this file.'}
</p>
<div className="mt-3 flex gap-3">
{!hasProtectionSources && (
<Button primary small onClick={() => application.showAccountMenu()}>
Open account menu
</Button>
)}
<Button primary onClick={() => application.protections.authorizeItemAccess(file)}>
{hasProtectionSources ? 'Authenticate' : 'View file'}
</Button>
</div>
</div>
)
}
@@ -17,7 +17,7 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
<div className="mb-2 text-base font-bold">This file can't be previewed.</div>
{isFilePreviewable ? (
<>
<div className="max-w-35ch mb-4 text-center text-sm text-passive-0">
<div className="mb-4 max-w-[35ch] text-center text-sm text-passive-0">
There was an error loading the file. Try again, or download the file and open it using another application.
</div>
<div className="flex items-center">
@@ -41,7 +41,7 @@ const FilePreviewError = ({ file, filesController, isFilePreviewable, tryAgainCa
</>
) : (
<>
<div className="max-w-35ch mb-4 text-center text-sm text-passive-0">
<div className="mb-4 max-w-[35ch] text-center text-sm text-passive-0">
To view this file, download it and open it using another application.
</div>
<Button
@@ -40,7 +40,14 @@ const AccountMenuButton = ({
</div>
</button>
</StyledTooltip>
<Popover anchorElement={buttonRef.current} open={isOpen} togglePopover={toggleMenu} side="top" className="py-2">
<Popover
anchorElement={buttonRef.current}
open={isOpen}
togglePopover={toggleMenu}
side="top"
align="start"
className="py-2"
>
<AccountMenu
onClickOutside={onClickOutside}
viewControllerManager={viewControllerManager}
@@ -46,6 +46,7 @@ export const IconNameToSvgMapping = {
'star-circle-filled': icons.StarCircleFilled,
'star-filled': icons.StarFilledIcon,
'star-variant-filled': icons.StarVariantFilledIcon,
'safe-square': icons.SafeSquareIcon,
'trash-filled': icons.TrashFilledIcon,
'trash-sweep': icons.TrashSweepIcon,
'user-add': icons.UserAddIcon,
@@ -3,6 +3,7 @@ import { FilesController } from '@/Controllers/FilesController'
import { FileItem } from '@standardnotes/snjs'
import { useState } from 'react'
import { PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
import { FileContextMenuBackupOption } from '../FileContextMenu/FileContextMenuBackupOption'
import Icon from '../Icon/Icon'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import Switch from '../Switch/Switch'
@@ -91,6 +92,8 @@ const LinkedFileMenuOptions = ({ file, closeMenu, handleFileAction, setIsRenamin
<Icon type="trash" className="mr-2 text-danger" />
<span className="text-danger">Delete permanently</span>
</button>
<FileContextMenuBackupOption file={file} />
</>
)
}
@@ -76,17 +76,20 @@ const MenuItem = forwardRef(
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
className={classNames(
'flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left md:py-1.5',
'flex w-full cursor-pointer border-0 bg-transparent px-3 py-2 text-left md:py-1.5',
'text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground',
'focus:bg-info-backdrop focus:shadow-none md:text-tablet-menu-item lg:text-menu-item',
className,
className.includes('items-') ? '' : 'items-center',
)}
onClick={onClick}
onBlur={onBlur}
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
>
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
{type === MenuItemType.IconButton && icon ? (
<Icon type={icon} className={`${iconClassName} flex-shrink-0`} />
) : null}
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
<RadioIndicator disabled={disabled} checked={checked} className="flex-shrink-0" />
) : null}
@@ -953,7 +953,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
)}
{editorMode === 'super' && (
<div className={classNames('blocks-editor w-full flex-grow overflow-hidden overflow-y-scroll')}>
<div className={classNames('blocks-editor w-full flex-grow overflow-hidden overflow-y-auto')}>
<SuperEditor
key={this.note.uuid}
application={this.application}
@@ -11,6 +11,7 @@ import { GetBulletedListBlock } from './Blocks/BulletedList'
import { GetChecklistBlock } from './Blocks/Checklist'
import { GetDividerBlock } from './Blocks/Divider'
import { GetCollapsibleBlock } from './Blocks/Collapsible'
import { GetDynamicPasswordBlocks, GetPasswordBlocks } from './Blocks/Password'
import { GetParagraphBlock } from './Blocks/Paragraph'
import { GetHeadingsBlocks } from './Blocks/Headings'
import { GetQuoteBlock } from './Blocks/Quote'
@@ -49,11 +50,15 @@ export default function BlockPickerMenuPlugin(): JSX.Element {
GetDividerBlock(editor),
...GetDatetimeBlocks(editor),
...GetAlignmentBlocks(editor),
...GetPasswordBlocks(editor),
GetCollapsibleBlock(editor),
...GetEmbedsBlocks(editor),
]
const dynamicOptions = GetDynamicTableBlocks(editor, queryString || '')
const dynamicOptions = [
...GetDynamicTableBlocks(editor, queryString || ''),
...GetDynamicPasswordBlocks(editor, queryString || ''),
]
return queryString
? [
@@ -108,16 +113,14 @@ export default function BlockPickerMenuPlugin(): JSX.Element {
return (
<Popover
align="start"
anchorPoint={{
x: anchorElementRef.current.offsetLeft,
y: anchorElementRef.current.offsetTop + (!isMobileScreen() ? anchorElementRef.current.offsetHeight : 0),
}}
anchorElement={anchorElementRef.current}
open={popoverOpen}
togglePopover={() => {
setPopoverOpen((prevValue) => !prevValue)
}}
disableMobileFullscreenTakeover={true}
side={isMobileScreen() ? 'top' : 'bottom'}
maxHeight={(mh) => mh / 2}
>
<div className={PopoverClassNames}>
<ul>
@@ -6,17 +6,17 @@ export function GetDatetimeBlocks(editor: LexicalEditor) {
return [
new BlockPickerOption('Current date and time', {
iconName: 'authenticator',
keywords: ['date'],
keywords: ['date', 'current'],
onSelect: () => editor.dispatchCommand(INSERT_DATETIME_COMMAND, 'datetime'),
}),
new BlockPickerOption('Current time', {
iconName: 'authenticator',
keywords: ['time'],
keywords: ['time', 'current'],
onSelect: () => editor.dispatchCommand(INSERT_TIME_COMMAND, 'datetime'),
}),
new BlockPickerOption('Current date', {
iconName: 'authenticator',
keywords: ['date'],
keywords: ['date', 'current'],
onSelect: () => editor.dispatchCommand(INSERT_DATE_COMMAND, 'datetime'),
}),
]
@@ -0,0 +1,42 @@
import { BlockPickerOption } from '../BlockPickerOption'
import { LexicalEditor } from 'lexical'
import { INSERT_PASSWORD_COMMAND } from '../../Commands'
const DEFAULT_PASSWORD_LENGTH = 16
const MIN_PASSWORD_LENGTH = 8
export function GetPasswordBlocks(editor: LexicalEditor) {
return [
new BlockPickerOption('Generate cryptographically secure password', {
iconName: 'password',
keywords: ['password', 'secure'],
onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, String(DEFAULT_PASSWORD_LENGTH)),
}),
]
}
export function GetDynamicPasswordBlocks(editor: LexicalEditor, queryString: string) {
if (queryString == null) {
return []
}
const lengthRegex = /^\d+$/
const match = lengthRegex.exec(queryString)
if (!match) {
return []
}
const length = parseInt(match[0], 10)
if (length < MIN_PASSWORD_LENGTH) {
return []
}
return [
new BlockPickerOption(`Generate ${length}-character cryptographically secure password`, {
iconName: 'password',
keywords: ['password', 'secure'],
onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, length.toString()),
}),
]
}
@@ -1,8 +1,8 @@
import { classNames } from '@/Utils/ConcatenateClassNames'
export const PopoverClassNames = classNames(
'z-dropdown-menu w-full min-w-80',
'cursor-auto flex-col overflow-y-auto rounded bg-default md:h-auto md:max-w-xs h-auto overflow-y-scroll',
'z-dropdown-menu w-full',
'cursor-auto flex-col overflow-y-auto rounded bg-default h-auto',
)
export const PopoverItemClassNames = classNames(
@@ -5,3 +5,4 @@ export const INSERT_BUBBLE_COMMAND: LexicalCommand<string> = createCommand('INSE
export const INSERT_TIME_COMMAND: LexicalCommand<string> = createCommand('INSERT_TIME_COMMAND')
export const INSERT_DATE_COMMAND: LexicalCommand<string> = createCommand('INSERT_DATE_COMMAND')
export const INSERT_DATETIME_COMMAND: LexicalCommand<string> = createCommand('INSERT_DATETIME_COMMAND')
export const INSERT_PASSWORD_COMMAND: LexicalCommand<string> = createCommand('INSERT_PASSWORD_COMMAND')
@@ -1,5 +1,5 @@
import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents'
import { useMemo } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { ElementFormatType, NodeKey } from 'lexical'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import FilePreview from '@/Components/FilePreview/FilePreview'
@@ -19,13 +19,47 @@ export function FileComponent({ className, format, nodeKey, fileUuid }: FileComp
const application = useApplication()
const file = useMemo(() => application.items.findItem<FileItem>(fileUuid), [application, fileUuid])
const [canLoad, setCanLoad] = useState(false)
const blockWrapperRef = useRef<HTMLDivElement>(null)
const blockObserver = useMemo(
() =>
new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setCanLoad(true)
}
})
},
{
threshold: 0.25,
},
),
[],
)
useEffect(() => {
const wrapper = blockWrapperRef.current
if (!wrapper) {
return
}
blockObserver.observe(wrapper)
return () => {
blockObserver.unobserve(wrapper)
}
}, [blockObserver])
if (!file) {
return <div>Unable to find file {fileUuid}</div>
}
return (
<BlockWithAlignableContents className={className} format={format} nodeKey={nodeKey}>
<FilePreview file={file} application={application} />
<div ref={blockWrapperRef}>{canLoad && <FilePreview file={file} application={application} />}</div>
</BlockWithAlignableContents>
)
}
@@ -105,16 +105,14 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
return (
<Popover
align="start"
anchorPoint={{
x: anchorElementRef.current.offsetLeft,
y: anchorElementRef.current.offsetTop + (!isMobileScreen() ? anchorElementRef.current.offsetHeight : 0),
}}
anchorElement={anchorElementRef.current}
open={popoverOpen}
togglePopover={() => {
setPopoverOpen((prevValue) => !prevValue)
}}
disableMobileFullscreenTakeover={true}
side={isMobileScreen() ? 'top' : 'bottom'}
maxHeight={(mh) => mh / 2}
>
<div className={PopoverClassNames}>
<ul>
@@ -0,0 +1,26 @@
const LOWER_CASE_LETTERS = 'abcdefghijklmnopqrstuvwxyz'.split('')
const UPPER_CASE_LETTERS = LOWER_CASE_LETTERS.map((l) => l.toUpperCase())
const SPECIAL_SYMBOLS = '!£$%^&*()@~:;,./?{}=-_'.split('')
const CHARACTER_SET = [...LOWER_CASE_LETTERS, ...UPPER_CASE_LETTERS, ...SPECIAL_SYMBOLS]
const CHARACTER_SET_LENGTH = CHARACTER_SET.length
function isValidPassword(password: string) {
const containsSymbols = SPECIAL_SYMBOLS.some((symbol) => password.includes(symbol))
const containsUpperCase = UPPER_CASE_LETTERS.some((upperLetter) => password.includes(upperLetter))
const containsLowerCase = LOWER_CASE_LETTERS.some((lowerLetter) => password.includes(lowerLetter))
return containsLowerCase && containsUpperCase && containsSymbols
}
export function generatePassword(length: number): string {
const buffer = new Uint8Array(length)
let generatedPassword = ''
do {
window.crypto.getRandomValues(buffer)
generatedPassword = [...buffer].map((x) => CHARACTER_SET[x % CHARACTER_SET_LENGTH]).join('')
} while (!isValidPassword(generatedPassword))
return generatedPassword
}
@@ -0,0 +1,40 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
COMMAND_PRIORITY_EDITOR,
$createTextNode,
$getSelection,
$isRangeSelection,
$createParagraphNode,
} from 'lexical'
import { useEffect } from 'react'
import { INSERT_PASSWORD_COMMAND } from '../Commands'
import { mergeRegister } from '@lexical/utils'
import { generatePassword } from './Generator'
export default function PasswordPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext()
useEffect(() => {
return mergeRegister(
editor.registerCommand<string>(
INSERT_PASSWORD_COMMAND,
(lengthString) => {
const length = Number(lengthString)
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
return false
}
const paragraph = $createParagraphNode()
const password = generatePassword(length)
paragraph.append($createTextNode(password))
selection.insertNodes([paragraph])
return true
},
COMMAND_PRIORITY_EDITOR,
),
)
}, [editor])
return null
}
@@ -21,6 +21,7 @@ import {
ChangeContentCallbackPlugin,
ChangeEditorFunction,
} from './Plugins/ChangeContentCallback/ChangeContentCallback'
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
const NotePreviewCharLimit = 160
@@ -102,7 +103,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
<BlocksEditor
onChange={handleChange}
ignoreFirstChange={true}
className="relative h-full resize-none px-5 py-4 text-base focus:shadow-none focus:outline-none"
className="relative h-full resize-none px-6 py-4 text-base focus:shadow-none focus:outline-none"
previewLength={NotePreviewCharLimit}
spellcheck={spellcheck}
>
@@ -111,6 +112,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
<ItemBubblePlugin />
<BlockPickerMenuPlugin />
<DatetimePlugin />
<PasswordPlugin />
<AutoLinkPlugin />
<ChangeContentCallbackPlugin
providerCallback={(callback) => (changeEditorFunction.current = callback)}
@@ -38,7 +38,6 @@ const NotesContextMenu = ({
}}
className="py-2"
open={contextMenuOpen}
side="right"
togglePopover={closeMenu}
>
<div className="select-none" ref={contextMenuRef}>
@@ -1,18 +1,31 @@
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
import { isMobileScreen } from '@/Utils'
import { CSSProperties } from 'react'
import { PopoverAlignment, PopoverSide } from './Types'
import { OppositeSide, checkCollisions, getNonCollidingSide, getNonCollidingAlignment } from './Utils/Collisions'
import { getPositionedPopoverRect } from './Utils/Rect'
import { OppositeSide, checkCollisions, getNonCollidingAlignment, getOverflows } from './Utils/Collisions'
import { getAppRect, getPopoverMaxHeight, getPositionedPopoverRect } from './Utils/Rect'
const getStylesFromRect = (
rect: DOMRect,
options: {
disableMobileFullscreenTakeover?: boolean
maxHeight?: number | 'none'
},
): CSSProperties => {
const { disableMobileFullscreenTakeover = false, maxHeight = 'none' } = options
const canApplyMaxHeight = maxHeight !== 'none' && (!isMobileScreen() || disableMobileFullscreenTakeover)
const getStylesFromRect = (rect: DOMRect, disableMobileFullscreenTakeover?: boolean): CSSProperties => {
return {
willChange: 'transform',
transform: `translate(${rect.x}px, ${rect.y}px)`,
...(disableMobileFullscreenTakeover
? {
maxWidth: `${window.innerWidth - rect.x * 2}px`,
}
: {}),
visibility: 'visible',
...(canApplyMaxHeight && {
maxHeight: `${maxHeight}px`,
}),
...(disableMobileFullscreenTakeover && {
maxWidth: `${window.innerWidth - rect.x * 2}px`,
}),
}
}
@@ -23,6 +36,7 @@ type Options = {
popoverRect?: DOMRect
side: PopoverSide
disableMobileFullscreenTakeover?: boolean
maxHeightFunction?: (calculatedMaxHeight: number) => number
}
export const getPositionedPopoverStyles = ({
@@ -32,31 +46,45 @@ export const getPositionedPopoverStyles = ({
popoverRect,
side,
disableMobileFullscreenTakeover,
}: Options): [CSSProperties | null, PopoverSide, PopoverAlignment] => {
maxHeightFunction,
}: Options): CSSProperties | null => {
if (!popoverRect || !anchorRect) {
return [null, side, align]
return null
}
const matchesMediumBreakpoint = matchMedia(MediaQueryBreakpoints.md).matches
if (!matchesMediumBreakpoint && !disableMobileFullscreenTakeover) {
return [null, side, align]
return null
}
const rectForPreferredSide = getPositionedPopoverRect(popoverRect, anchorRect, side, align)
const preferredSideRectCollisions = checkCollisions(rectForPreferredSide, documentRect)
const preferredSideOverflows = getOverflows(rectForPreferredSide, documentRect)
const oppositeSide = OppositeSide[side]
const rectForOppositeSide = getPositionedPopoverRect(popoverRect, anchorRect, oppositeSide, align)
const oppositeSideRectCollisions = checkCollisions(rectForOppositeSide, documentRect)
const oppositeSideOverflows = getOverflows(rectForOppositeSide, documentRect)
const finalSide = getNonCollidingSide(side, preferredSideRectCollisions, oppositeSideRectCollisions)
const finalAlignment = getNonCollidingAlignment(finalSide, align, preferredSideRectCollisions, {
const sideWithLessOverflows = preferredSideOverflows[side] < oppositeSideOverflows[oppositeSide] ? side : oppositeSide
const finalAlignment = getNonCollidingAlignment(sideWithLessOverflows, align, preferredSideRectCollisions, {
popoverRect,
buttonRect: anchorRect,
documentRect,
})
const finalPositionedRect = getPositionedPopoverRect(popoverRect, anchorRect, finalSide, finalAlignment)
const finalPositionedRect = getPositionedPopoverRect(popoverRect, anchorRect, sideWithLessOverflows, finalAlignment)
return [getStylesFromRect(finalPositionedRect, disableMobileFullscreenTakeover), finalSide, finalAlignment]
let maxHeight = getPopoverMaxHeight(
getAppRect(),
anchorRect,
sideWithLessOverflows,
finalAlignment,
disableMobileFullscreenTakeover,
)
if (maxHeightFunction && typeof maxHeight === 'number') {
maxHeight = maxHeightFunction(maxHeight)
}
return getStylesFromRect(finalPositionedRect, { disableMobileFullscreenTakeover, maxHeight })
}
@@ -41,6 +41,7 @@ const Popover = ({
togglePopover,
disableClickOutside,
disableMobileFullscreenTakeover,
maxHeight,
}: Props) => {
const popoverId = useRef(UuidGenerator.GenerateUuid())
@@ -93,13 +94,14 @@ const Popover = ({
anchorElement={anchorElement}
anchorPoint={anchorPoint}
childPopovers={childPopovers}
className={className}
className={`popover-content-container ${className ?? ''}`}
id={popoverId.current}
overrideZIndex={overrideZIndex}
side={side}
togglePopover={togglePopover}
disableClickOutside={disableClickOutside}
disableMobileFullscreenTakeover={disableMobileFullscreenTakeover}
maxHeight={maxHeight}
>
{children}
</PositionedPopoverContent>
@@ -7,7 +7,6 @@ import Portal from '../Portal/Portal'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
import { PopoverContentProps } from './Types'
import { getPopoverMaxHeight, getAppRect } from './Utils/Rect'
import { usePopoverCloseOnClickOutside } from './Utils/usePopoverCloseOnClickOutside'
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
@@ -25,6 +24,7 @@ const PositionedPopoverContent = ({
togglePopover,
disableClickOutside,
disableMobileFullscreenTakeover,
maxHeight,
}: PopoverContentProps) => {
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
const popoverRect = useAutoElementRect(popoverElement)
@@ -39,13 +39,14 @@ const PositionedPopoverContent = ({
const documentRect = useDocumentRect()
const isDesktopScreen = useMediaQuery(MediaQueryBreakpoints.md)
const [styles, positionedSide, positionedAlignment] = getPositionedPopoverStyles({
const styles = getPositionedPopoverStyles({
align,
anchorRect,
documentRect,
popoverRect: popoverRect ?? popoverElement?.getBoundingClientRect(),
side,
disableMobileFullscreenTakeover: disableMobileFullscreenTakeover,
maxHeightFunction: maxHeight,
})
usePopoverCloseOnClickOutside({
@@ -79,20 +80,10 @@ const PositionedPopoverContent = ({
!disableMobileFullscreenTakeover && 'h-full',
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
!isDesktopScreen && !disableMobileFullscreenTakeover ? 'pt-safe-top pb-safe-bottom' : '',
!styles && 'md:invisible',
isDesktopScreen || disableMobileFullscreenTakeover ? 'invisible' : '',
)}
style={{
...styles,
maxHeight: styles
? getPopoverMaxHeight(
getAppRect(documentRect),
anchorRect,
positionedSide,
positionedAlignment,
disableMobileFullscreenTakeover,
)
: '',
top: !isDesktopScreen ? `${document.documentElement.scrollTop}px` : '',
}}
ref={setPopoverElement}
data-popover={id}
@@ -39,6 +39,7 @@ type CommonPopoverProps = {
className?: string
disableClickOutside?: boolean
disableMobileFullscreenTakeover?: boolean
maxHeight?: (calculatedMaxHeight: number) => number
}
export type PopoverContentProps = CommonPopoverProps & {
@@ -8,6 +8,17 @@ export const OppositeSide: Record<PopoverSide, PopoverSide> = {
right: 'left',
}
export const getOverflows = (popoverRect: DOMRect, documentRect: DOMRect): Record<PopoverSide, number> => {
const overflows = {
top: documentRect.top - popoverRect.top,
bottom: popoverRect.height - (documentRect.bottom - popoverRect.top),
left: documentRect.left - popoverRect.left,
right: popoverRect.right - documentRect.right,
}
return overflows
}
export const checkCollisions = (popoverRect: DOMRect, containerRect: DOMRect): RectCollisions => {
const appRect = getAppRect(containerRect)
@@ -49,15 +49,6 @@ export const getPopoverMaxHeight = (
return appRect.height - constraint - MarginFromAppBorderInPX
}
export const getMaxHeightAdjustedRect = (rect: DOMRect, maxHeight: number) => {
return DOMRect.fromRect({
width: rect.width,
height: rect.height < maxHeight ? rect.height : maxHeight,
x: rect.x,
y: rect.y,
})
}
export const getAppRect = (updatedDocumentRect?: DOMRect) => {
const footerRect = document.querySelector('footer')?.getBoundingClientRect()
const documentRect = updatedDocumentRect ? updatedDocumentRect : document.documentElement.getBoundingClientRect()
@@ -21,7 +21,7 @@ const FileBackupsCrossPlatform = ({ application }: Props) => {
<PreferencesGroup>
<PreferencesSegment>
<Title>File Backups</Title>
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
<Subtitle>Automatically save encrypted backups of files uploaded on any device to this computer.</Subtitle>
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
@@ -29,6 +29,14 @@ export const formatDateAndTimeForNote = (date: Date, includeTime = true) => {
}
}
export const dateToStringStyle1 = (date: Date) => {
const dateString = `${date.toLocaleDateString()}`
return `${dateString} at ${date.toLocaleTimeString(undefined, {
timeStyle: 'short',
})}`
}
export const dateToHoursAndMinutesTimeString = (date: Date) => {
return date.toLocaleTimeString(undefined, {
timeStyle: 'short',
+1
View File
@@ -6264,6 +6264,7 @@ __metadata:
lodash: ^4.17.21
reflect-metadata: ^0.1.13
ts-jest: ^29.0.3
typescript: "*"
languageName: unknown
linkType: soft