mirror of
https://github.com/standardnotes/app
synced 2026-09-19 00:13:54 -04:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8755001f7e | ||
|
|
7c2e832065 | ||
|
|
096d82f7af | ||
|
|
8c8f045b9a | ||
|
|
b3140f1623 | ||
|
|
c5e104f90b | ||
|
|
09b8f00eed | ||
|
|
c39c72da7a | ||
|
|
7f28876047 | ||
|
|
24ff87c306 | ||
|
|
e8b758214d | ||
|
|
e58c563795 | ||
|
|
328bbb797d | ||
|
|
dab4f678f0 | ||
|
|
c847cc1d16 | ||
|
|
f49ba6bd4d | ||
|
|
0309912f98 | ||
|
|
0c253dc46a | ||
|
|
4b081890fd | ||
|
|
87f86693a6 | ||
|
|
bc12663c7f | ||
|
|
f793673875 | ||
|
|
dcc4353865 | ||
|
|
b9e6dc4c2b | ||
|
|
e6baf74d14 | ||
|
|
a6e57e30cf | ||
|
|
33b25d4b7a | ||
|
|
55b6714794 | ||
|
|
54b1a14bcd | ||
|
|
fd170cfd0e |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,22 @@
|
||||
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.20.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.20.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.20.5",
|
||||
"version": "1.20.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { SubscriptionInviteAcceptResponse } from '../../Response/Subscription/SubscriptionInviteAcceptResponse'
|
||||
import { SubscriptionInviteCancelResponse } from '../../Response/Subscription/SubscriptionInviteCancelResponse'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ProtocolVersion, UserRequestType } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UserRequestType, Uuid } from '@standardnotes/common'
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
import { type RootKeyParamsInterface } from '@standardnotes/models'
|
||||
|
||||
import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
export type SubscriptionInviteListResponseBody = {
|
||||
invitations: Array<Invitation>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { type Invitation } from '@standardnotes/models'
|
||||
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
* improved error reporting in super; improved draggable block icon padding ([24ff87c](https://github.com/standardnotes/app/commit/24ff87c30625432393ffedeada14478ebb625292))
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue where converting empty plain note to super would result in error ([e58c563](https://github.com/standardnotes/app/commit/e58c5637951f928efcd8c8427f06d0b6c69b5e55))
|
||||
|
||||
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **web:** enable block drag'n'drop in super editor ([#2029](https://github.com/standardnotes/app/issues/2029)) ([dab4f67](https://github.com/standardnotes/app/commit/dab4f678f04aee2c7dfbe7f416e7fc34e8d870cf))
|
||||
|
||||
## [1.5.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/blocks-editor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/blocks-editor",
|
||||
"version": "1.5.3",
|
||||
"version": "1.7.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -31,8 +31,6 @@ import FloatingLinkEditorPlugin from '../Lexical/Plugins/FloatingLinkEditorPlugi
|
||||
import {truncateString} from './Utils';
|
||||
import {SuperEditorContentId} from './Constants';
|
||||
|
||||
const BlockDragEnabled = false;
|
||||
|
||||
type BlocksEditorProps = {
|
||||
onChange: (value: string, preview: string) => void;
|
||||
className?: string;
|
||||
@@ -69,8 +67,14 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
});
|
||||
previewText = truncateString(previewText, previewLength);
|
||||
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange(stringifiedEditorState, previewText);
|
||||
try {
|
||||
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
|
||||
onChange(stringifiedEditorState, previewText);
|
||||
} catch (error) {
|
||||
window.alert(
|
||||
`An invalid change was made inside the Super editor. Your change was not saved. Please report this error to the team: ${error}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
[onChange, didIgnoreFirstChange],
|
||||
@@ -94,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>
|
||||
@@ -130,11 +134,9 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
<>
|
||||
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
|
||||
<FloatingLinkEditorPlugin />
|
||||
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
|
||||
</>
|
||||
)}
|
||||
{floatingAnchorElem && BlockDragEnabled && (
|
||||
<>{<DraggableBlockPlugin anchorElem={floatingAnchorElem} />}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import {BlockEditorNodes} from '../Lexical/Nodes/AllNodes';
|
||||
import {Klass, LexicalNode} from 'lexical';
|
||||
|
||||
type BlocksEditorComposerProps = {
|
||||
initialValue: string;
|
||||
initialValue: string | undefined;
|
||||
children: React.ReactNode;
|
||||
nodes?: Array<Klass<LexicalNode>>;
|
||||
readonly?: boolean;
|
||||
|
||||
@@ -1,19 +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: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.3;
|
||||
background-image: url(#{$blocks-editor-icons-path}/draggable-block-menu.svg);
|
||||
width: 0.8rem;
|
||||
height: 1.1rem;
|
||||
opacity: 0.2;
|
||||
padding-left: 4.75px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.draggable-block-menu:active {
|
||||
@@ -21,16 +23,17 @@
|
||||
}
|
||||
|
||||
.draggable-block-menu:hover {
|
||||
background-color: #efefef;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
}
|
||||
|
||||
.draggable-block-target-line {
|
||||
pointer-events: none;
|
||||
background: deepskyblue;
|
||||
height: 4px;
|
||||
background: var(--sn-stylekit-info-color);
|
||||
height: 0.25rem;
|
||||
position: absolute;
|
||||
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,16 +20,17 @@ import {
|
||||
} from 'lexical';
|
||||
import {DragEvent as ReactDragEvent, useEffect, useRef, useState} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
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;
|
||||
@@ -52,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;
|
||||
@@ -72,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
|
||||
@@ -123,7 +159,6 @@ function setMenuPosition(
|
||||
) {
|
||||
if (!targetElem) {
|
||||
floatingElem.style.opacity = '0';
|
||||
floatingElem.style.transform = 'translate(-10000px, -10000px)';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,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)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,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);
|
||||
|
||||
@@ -347,7 +393,9 @@ function useDraggableBlockMenu(
|
||||
draggable={true}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}>
|
||||
<div className={isEditable ? 'icon' : ''} />
|
||||
<div className={isEditable ? 'icon' : ''}>
|
||||
<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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {isPoint, Point} from './point';
|
||||
|
||||
type ContainsPointReturn = {
|
||||
export type ContainsPointReturn = {
|
||||
result: boolean;
|
||||
reason: {
|
||||
isOnTopSide: boolean;
|
||||
|
||||
@@ -3,6 +3,64 @@
|
||||
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
|
||||
|
||||
## [3.100.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.100.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**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,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.100.5",
|
||||
"version": "3.101.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
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.19.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.13",
|
||||
"version": "1.19.17",
|
||||
"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.55.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* distinguish client controlled features so that server expiration timestamps are ignored ([#2022](https://github.com/standardnotes/app/issues/2022)) ([a6e57e3](https://github.com/standardnotes/app/commit/a6e57e30cff64de83eaedf3d36117a497a7a70ae))
|
||||
|
||||
## [1.55.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -23,6 +23,9 @@ export type BaseFeatureDescription = RoleFields & {
|
||||
description?: string
|
||||
expires_at?: number
|
||||
|
||||
/** Whether the client controls availability of this feature (such as the dark theme) */
|
||||
clientControlled?: boolean
|
||||
|
||||
flags?: ComponentFlag[]
|
||||
identifier: FeatureIdentifier
|
||||
marketing_url?: string
|
||||
|
||||
@@ -10,8 +10,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Midnight',
|
||||
identifier: FeatureIdentifier.MidnightTheme,
|
||||
permission_name: PermissionName.MidnightTheme,
|
||||
description: 'Elegant utilitarianism.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/midnight-with-mobile.jpg',
|
||||
isDark: true,
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
@@ -26,8 +24,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Futura',
|
||||
identifier: FeatureIdentifier.FuturaTheme,
|
||||
permission_name: PermissionName.FuturaTheme,
|
||||
description: 'Calm and relaxed. Take some time off.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/futura-with-mobile.jpg',
|
||||
isDark: true,
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
@@ -42,8 +38,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Solarized Dark',
|
||||
identifier: FeatureIdentifier.SolarizedDarkTheme,
|
||||
permission_name: PermissionName.SolarizedDarkTheme,
|
||||
description: 'The perfect theme for any time.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/solarized-dark.jpg',
|
||||
isDark: true,
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
@@ -58,8 +52,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Autobiography',
|
||||
identifier: FeatureIdentifier.AutobiographyTheme,
|
||||
permission_name: PermissionName.AutobiographyTheme,
|
||||
description: 'A theme for writers and readers.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/autobiography.jpg',
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
background_color: '#9D7441',
|
||||
@@ -73,8 +65,7 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Dark',
|
||||
identifier: FeatureIdentifier.DarkTheme,
|
||||
permission_name: PermissionName.FocusedTheme,
|
||||
description: 'For when you need to go in.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/focus-with-mobile.jpg',
|
||||
clientControlled: true,
|
||||
isDark: true,
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
@@ -89,8 +80,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
name: 'Titanium',
|
||||
identifier: FeatureIdentifier.TitaniumTheme,
|
||||
permission_name: PermissionName.TitaniumTheme,
|
||||
description: 'Light on the eyes, heavy on the spirit.',
|
||||
thumbnail_url: 'https://s3.amazonaws.com/standard-notes/screenshots/models/themes/titanium-with-mobile.jpg',
|
||||
dock_icon: {
|
||||
type: 'circle',
|
||||
background_color: '#6e2b9e',
|
||||
@@ -106,7 +95,6 @@ export function themes(): ThemeFeatureDescription[] {
|
||||
permission_name: PermissionName.ThemeDynamic,
|
||||
layerable: true,
|
||||
no_mobile: true,
|
||||
description: 'A smart theme that minimizes the tags and notes panels when they are not in use.',
|
||||
})
|
||||
|
||||
return [midnight, futura, solarizedDark, autobiography, dark, titanium, dynamic]
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
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
|
||||
|
||||
## [1.25.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.25.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.25.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.25.13",
|
||||
"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,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"extends": "../../UILib.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
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
|
||||
|
||||
## [1.12.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.12.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.12.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.12.13",
|
||||
"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,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"extends": "../../UILib.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
|
||||
@@ -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.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
|
||||
|
||||
* **web:** enable block drag'n'drop in super editor ([#2029](https://github.com/standardnotes/app/issues/2029)) ([dab4f67](https://github.com/standardnotes/app/commit/dab4f678f04aee2c7dfbe7f416e7fc34e8d870cf))
|
||||
|
||||
## [1.4.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/icons
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/icons",
|
||||
"version": "1.4.10",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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 |
@@ -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,
|
||||
|
||||
@@ -27,6 +27,7 @@ import TypeSubscript from './type-subscript.svg'
|
||||
import TypeSuperscript from './type-superscript.svg'
|
||||
import TypeUnderline from './type-underline.svg'
|
||||
import LexicalPencilFill from './pencil-fill.svg'
|
||||
import LexicalDraggableBlockMenu from './draggable-block-menu.svg'
|
||||
|
||||
export {
|
||||
LexicalCaretRightFill,
|
||||
@@ -58,4 +59,5 @@ export {
|
||||
TypeSuperscript,
|
||||
TypeUnderline,
|
||||
LexicalPencilFill,
|
||||
LexicalDraggableBlockMenu,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
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
|
||||
|
||||
## [3.46.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.46.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.46.15",
|
||||
"version": "3.46.29",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
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.37.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** keep apply payload timestamp when using keep base conflict strategy ([#2031](https://github.com/standardnotes/app/issues/2031)) ([87f8669](https://github.com/standardnotes/app/commit/87f86693a67a36972f0e44a883b18a7129c36893))
|
||||
|
||||
## [1.37.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.37.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.37.2",
|
||||
"version": "1.37.6",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "Models used in SNJS library",
|
||||
"author": "Standard Notes",
|
||||
"main": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"tsc": "tsc -p tsconfig.json",
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -99,4 +99,22 @@ describe('conflict delta', () => {
|
||||
|
||||
expect(delta.getConflictStrategy()).toBe(ConflictStrategy.KeepApply)
|
||||
})
|
||||
|
||||
it('if keep base strategy, always use the apply payloads updated_at_timestamp', () => {
|
||||
const basePayload = createDecryptedItemsKey('123', 'secret', 2)
|
||||
|
||||
const baseCollection = createBaseCollection(basePayload)
|
||||
|
||||
const applyPayload = createDecryptedItemsKey('123', 'other secret', 1)
|
||||
|
||||
const delta = new ConflictDelta(baseCollection, basePayload, applyPayload, historyMap)
|
||||
|
||||
expect(delta.getConflictStrategy()).toBe(ConflictStrategy.KeepBaseDuplicateApply)
|
||||
|
||||
const result = delta.result()
|
||||
|
||||
expect(result.emits).toHaveLength(1)
|
||||
|
||||
expect(result.emits[0].updated_at_timestamp).toEqual(applyPayload.updated_at_timestamp)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { greaterOfTwoDates, uniqCombineObjArrays } from '@standardnotes/utils'
|
||||
import { uniqCombineObjArrays } from '@standardnotes/utils'
|
||||
import { ImmutablePayloadCollection } from '../Collection/Payload/ImmutablePayloadCollection'
|
||||
import { CreateDecryptedItemFromPayload, CreateItemFromPayload } from '../../Utilities/Item/ItemGenerator'
|
||||
import { HistoryMap, historyMapFunctions } from '../History/HistoryMap'
|
||||
@@ -114,9 +114,9 @@ export class ConflictDelta {
|
||||
}
|
||||
|
||||
private handleKeepBaseStrategy(): SyncResolvedPayload[] {
|
||||
const updatedAt = greaterOfTwoDates(this.basePayload.serverUpdatedAt, this.applyPayload.serverUpdatedAt)
|
||||
const updatedAt = this.applyPayload.serverUpdatedAt
|
||||
|
||||
const updatedAtTimestamp = Math.max(this.basePayload.updated_at_timestamp, this.applyPayload.updated_at_timestamp)
|
||||
const updatedAtTimestamp = this.applyPayload.updated_at_timestamp
|
||||
|
||||
const leftPayload = this.basePayload.copyAsSyncResolved(
|
||||
{
|
||||
@@ -146,9 +146,9 @@ export class ConflictDelta {
|
||||
}
|
||||
|
||||
private handleKeepBaseDuplicateApplyStrategy(): SyncResolvedPayload[] {
|
||||
const updatedAt = greaterOfTwoDates(this.basePayload.serverUpdatedAt, this.applyPayload.serverUpdatedAt)
|
||||
const updatedAt = this.applyPayload.serverUpdatedAt
|
||||
|
||||
const updatedAtTimestamp = Math.max(this.basePayload.updated_at_timestamp, this.applyPayload.updated_at_timestamp)
|
||||
const updatedAtTimestamp = this.applyPayload.updated_at_timestamp
|
||||
|
||||
const leftPayload = this.basePayload.copyAsSyncResolved(
|
||||
{
|
||||
@@ -201,9 +201,9 @@ export class ConflictDelta {
|
||||
'content_type',
|
||||
])
|
||||
|
||||
const updatedAt = greaterOfTwoDates(this.basePayload.serverUpdatedAt, this.applyPayload.serverUpdatedAt)
|
||||
const updatedAt = this.applyPayload.serverUpdatedAt
|
||||
|
||||
const updatedAtTimestamp = Math.max(this.basePayload.updated_at_timestamp, this.applyPayload.updated_at_timestamp)
|
||||
const updatedAtTimestamp = this.applyPayload.updated_at_timestamp
|
||||
|
||||
const payload = this.basePayload.copyAsSyncResolved(
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../UILib.tsconfig.json",
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
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.3.251](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.250](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.249](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.248](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.247](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.246](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.245](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.244](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.243](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.242](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.241](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.241",
|
||||
"version": "1.3.255",
|
||||
"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.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.12.8](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.8",
|
||||
"version": "1.12.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
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.45.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.45.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.45.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.45.3",
|
||||
"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'> {
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
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
|
||||
|
||||
## [2.151.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **snjs:** keep apply payload timestamp when using keep base conflict strategy ([#2031](https://github.com/standardnotes/app/issues/2031)) ([87f8669](https://github.com/standardnotes/app/commit/87f86693a67a36972f0e44a883b18a7129c36893))
|
||||
|
||||
## [2.151.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* distinguish client controlled features so that server expiration timestamps are ignored ([#2022](https://github.com/standardnotes/app/issues/2022)) ([a6e57e3](https://github.com/standardnotes/app/commit/a6e57e30cff64de83eaedf3d36117a497a7a70ae))
|
||||
|
||||
## [2.151.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle cleared data on firefox ([#2021](https://github.com/standardnotes/app/issues/2021)) ([55b6714](https://github.com/standardnotes/app/commit/55b671479451d8d7e291a9875e227abc917d30b5))
|
||||
|
||||
## [2.151.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.151.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { SNLog } from '@Lib/Log'
|
||||
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
|
||||
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
|
||||
import { Migration } from '@Lib/Migrations/Migration'
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
SessionStrings,
|
||||
Challenge,
|
||||
} from '@standardnotes/services'
|
||||
import { isNullOrUndefined } from '@standardnotes/utils'
|
||||
import { assert } from '@standardnotes/utils'
|
||||
import { CreateReader } from './StorageReaders/Functions'
|
||||
import { StorageReader } from './StorageReaders/Reader'
|
||||
import { ContentTypeUsesRootKeyEncryption } from '@standardnotes/encryption'
|
||||
@@ -77,7 +76,7 @@ export class BaseMigration extends Migration {
|
||||
/** Coming from 2.0.0 (which did not store version) OR is brand new application */
|
||||
const migrationKey = namespacedKey(this.services.identifier, LastMigrationTimeStampKey2_0_0)
|
||||
const migrationValue = await this.services.deviceInterface.getRawStorageValue(migrationKey)
|
||||
const is_2_0_0_application = !isNullOrUndefined(migrationValue)
|
||||
const is_2_0_0_application = migrationValue != undefined
|
||||
if (is_2_0_0_application) {
|
||||
await this.services.deviceInterface.setRawStorageValue(storageKey, PreviousSnjsVersion2_0_0)
|
||||
await this.services.deviceInterface.removeRawStorageValue(LastMigrationTimeStampKey2_0_0)
|
||||
@@ -141,7 +140,7 @@ export class BaseMigration extends Migration {
|
||||
}
|
||||
|
||||
const rawAccountParams = await this.reader.getAccountKeyParams()
|
||||
const hasAccountKeyParams = !isNullOrUndefined(rawAccountParams)
|
||||
const hasAccountKeyParams = rawAccountParams != undefined
|
||||
if (!hasAccountKeyParams) {
|
||||
/** Doesn't apply if account is not involved */
|
||||
this.memoizedNeedsKeychainRepair = false
|
||||
@@ -169,7 +168,30 @@ export class BaseMigration extends Migration {
|
||||
const version = (await this.getStoredVersion()) as string
|
||||
const rawAccountParams = await this.reader.getAccountKeyParams()
|
||||
|
||||
/** Challenge for account password */
|
||||
/** Choose an item to decrypt against */
|
||||
const allItems = (
|
||||
await this.services.deviceInterface.getAllRawDatabasePayloads<EncryptedTransferPayload>(this.services.identifier)
|
||||
).map((p) => new EncryptedPayload(p))
|
||||
|
||||
let itemToDecrypt = allItems.find((item) => {
|
||||
return ContentTypeUsesRootKeyEncryption(item.content_type)
|
||||
})
|
||||
|
||||
if (!itemToDecrypt) {
|
||||
/** If no root key encrypted item, choose any item */
|
||||
itemToDecrypt = allItems[0]
|
||||
}
|
||||
|
||||
if (!itemToDecrypt) {
|
||||
/**
|
||||
* No items to decrypt, user probably cleared their browser data. Only choice is to clear storage
|
||||
* as any remainign account data is useless without items
|
||||
*/
|
||||
await this.services.storageService.clearValues()
|
||||
return
|
||||
}
|
||||
|
||||
/** Prompt for account password */
|
||||
const challenge = new Challenge(
|
||||
[new ChallengePrompt(ChallengeValidation.None, undefined, SessionStrings.PasswordInputPlaceholder, true)],
|
||||
ChallengeReason.Custom,
|
||||
@@ -185,25 +207,8 @@ export class BaseMigration extends Migration {
|
||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent)
|
||||
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
|
||||
|
||||
/** Choose an item to decrypt */
|
||||
const allItems = (
|
||||
await this.services.deviceInterface.getAllRawDatabasePayloads<EncryptedTransferPayload>(
|
||||
this.services.identifier,
|
||||
)
|
||||
).map((p) => new EncryptedPayload(p))
|
||||
|
||||
let itemToDecrypt = allItems.find((item) => {
|
||||
return ContentTypeUsesRootKeyEncryption(item.content_type)
|
||||
})
|
||||
|
||||
if (!itemToDecrypt) {
|
||||
/** If no root key encrypted item, just choose any item */
|
||||
itemToDecrypt = allItems[0]
|
||||
}
|
||||
|
||||
if (!itemToDecrypt) {
|
||||
throw SNLog.error(Error('Attempting keychain recovery validation but no items present.'))
|
||||
}
|
||||
/** TS can't detect we returned early above if itemToDecrypt is null */
|
||||
assert(itemToDecrypt)
|
||||
|
||||
const decryptedPayload = await this.services.protocolService.decryptSplitSingle({
|
||||
usesRootKey: {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -479,16 +479,35 @@ describe('featuresService', () => {
|
||||
|
||||
const featuresService = createService()
|
||||
const nativeFeature = featuresService['mapRemoteNativeFeatureToStaticFeature'](remoteFeature)
|
||||
featuresService['mapNativeFeatureToItem'] = jest.fn()
|
||||
featuresService['mapRemoteNativeFeatureToItem'] = jest.fn()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
expect(featuresService['mapNativeFeatureToItem']).toHaveBeenCalledWith(
|
||||
expect(featuresService['mapRemoteNativeFeatureToItem']).toHaveBeenCalledWith(
|
||||
nativeFeature,
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('mapRemoteNativeFeatureToItem should throw if called with client controlled feature', async () => {
|
||||
const clientFeature = {
|
||||
identifier: FeatureIdentifier.DarkTheme,
|
||||
content_type: ContentType.Theme,
|
||||
clientControlled: true,
|
||||
} as FeatureDescription
|
||||
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
apiService.getUserFeatures = jest.fn().mockReturnValue({
|
||||
data: {
|
||||
features: [clientFeature],
|
||||
},
|
||||
})
|
||||
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await expect(() => featuresService['mapRemoteNativeFeatureToItem'](clientFeature, [], [])).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('feature status', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
@@ -649,6 +668,16 @@ describe('featuresService', () => {
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('didDownloadFeatures should filter out client controlled features', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
featuresService['mapRemoteNativeFeaturesToItems'] = jest.fn()
|
||||
|
||||
await featuresService.didDownloadFeatures(GetFeatures().filter((f) => f.clientControlled))
|
||||
|
||||
expect(featuresService['mapRemoteNativeFeaturesToItems']).toHaveBeenCalledWith([])
|
||||
})
|
||||
|
||||
it('feature status should be dynamic for subscriber if cached features and no successful features request made yet', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ export class SNFeaturesService
|
||||
await super.handleApplicationStage(stage)
|
||||
|
||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
||||
void this.addDarkTheme()
|
||||
void this.mapClientControlledFeaturesToItems()
|
||||
|
||||
if (!this.rolesIncludePaidSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
@@ -163,11 +163,32 @@ export class SNFeaturesService
|
||||
}
|
||||
}
|
||||
|
||||
private async addDarkTheme() {
|
||||
const darkThemeFeature = FeaturesImports.FindNativeFeature(FeatureIdentifier.DarkTheme)
|
||||
private async mapClientControlledFeaturesToItems() {
|
||||
const clientFeatures = FeaturesImports.GetFeatures().filter((feature) => feature.clientControlled)
|
||||
const currentItems = this.itemManager.getItems<Models.SNComponent>([ContentType.Component, ContentType.Theme])
|
||||
|
||||
if (darkThemeFeature) {
|
||||
await this.mapRemoteNativeFeaturesToItems([darkThemeFeature])
|
||||
for (const feature of clientFeatures) {
|
||||
if (!feature.content_type) {
|
||||
continue
|
||||
}
|
||||
|
||||
const existingItem = currentItems.find((item) => item.identifier === feature.identifier)
|
||||
if (existingItem) {
|
||||
const hasChange = JSON.stringify(feature) !== JSON.stringify(existingItem.package_info)
|
||||
if (hasChange) {
|
||||
await this.itemManager.changeComponent(existingItem, (mutator) => {
|
||||
mutator.package_info = feature
|
||||
})
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.itemManager.createItem(
|
||||
feature.content_type,
|
||||
this.componentContentForNativeFeatureDescription(feature),
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +417,10 @@ export class SNFeaturesService
|
||||
|
||||
public async didDownloadFeatures(features: FeaturesImports.FeatureDescription[]): Promise<void> {
|
||||
features = features
|
||||
.filter((feature) => !!FeaturesImports.FindNativeFeature(feature.identifier))
|
||||
.filter((feature) => {
|
||||
const nativeFeature = FeaturesImports.FindNativeFeature(feature.identifier)
|
||||
return nativeFeature != undefined && !nativeFeature.clientControlled
|
||||
})
|
||||
.map((feature) => this.mapRemoteNativeFeatureToStaticFeature(feature))
|
||||
|
||||
this.features = features
|
||||
@@ -436,6 +460,7 @@ export class SNFeaturesService
|
||||
if (nativeFeatureCopy.expires_at) {
|
||||
nativeFeatureCopy.expires_at = convertTimestampToMilliseconds(nativeFeatureCopy.expires_at)
|
||||
}
|
||||
|
||||
return nativeFeatureCopy
|
||||
}
|
||||
|
||||
@@ -563,32 +588,41 @@ export class SNFeaturesService
|
||||
let hasChanges = false
|
||||
|
||||
for (const feature of features) {
|
||||
const didChange = await this.mapNativeFeatureToItem(feature, currentItems, itemsToDelete)
|
||||
const didChange = await this.mapRemoteNativeFeatureToItem(feature, currentItems, itemsToDelete)
|
||||
if (didChange) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
|
||||
await this.itemManager.setItemsToBeDeleted(itemsToDelete)
|
||||
|
||||
if (hasChanges) {
|
||||
void this.syncService.sync()
|
||||
}
|
||||
}
|
||||
|
||||
private async mapNativeFeatureToItem(
|
||||
private async mapRemoteNativeFeatureToItem(
|
||||
feature: FeaturesImports.FeatureDescription,
|
||||
currentItems: Models.SNComponent[],
|
||||
itemsToDelete: Models.SNComponent[],
|
||||
): Promise<boolean> {
|
||||
if (feature.clientControlled) {
|
||||
throw new Error('Attempted to map client controlled feature as remote item')
|
||||
}
|
||||
|
||||
if (!feature.content_type) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.isExperimentalFeature(feature.identifier) && !this.isExperimentalFeatureEnabled(feature.identifier)) {
|
||||
const isDisabledExperimentalFeature =
|
||||
this.isExperimentalFeature(feature.identifier) && !this.isExperimentalFeatureEnabled(feature.identifier)
|
||||
|
||||
if (isDisabledExperimentalFeature) {
|
||||
return false
|
||||
}
|
||||
|
||||
let hasChanges = false
|
||||
|
||||
const now = new Date()
|
||||
const expired = this.isFreeFeature(feature.identifier)
|
||||
? false
|
||||
@@ -599,6 +633,7 @@ export class SNFeaturesService
|
||||
const itemIdentifier = item.content.package_info.identifier
|
||||
return itemIdentifier === feature.identifier
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -610,14 +645,17 @@ export class SNFeaturesService
|
||||
|
||||
if (existingItem) {
|
||||
const featureExpiresAt = new Date(feature.expires_at || 0)
|
||||
const hasChange =
|
||||
JSON.stringify(feature) !== JSON.stringify(existingItem.package_info) ||
|
||||
featureExpiresAt.getTime() !== existingItem.valid_until.getTime()
|
||||
const hasChangeInPackageInfo = JSON.stringify(feature) !== JSON.stringify(existingItem.package_info)
|
||||
const hasChangeInExpiration = featureExpiresAt.getTime() !== existingItem.valid_until.getTime()
|
||||
|
||||
const hasChange = hasChangeInPackageInfo || hasChangeInExpiration
|
||||
|
||||
if (hasChange) {
|
||||
resultingItem = await this.itemManager.changeComponent(existingItem, (mutator) => {
|
||||
mutator.package_info = feature
|
||||
mutator.valid_until = featureExpiresAt
|
||||
})
|
||||
|
||||
hasChanges = true
|
||||
} else {
|
||||
resultingItem = existingItem
|
||||
|
||||
@@ -77,6 +77,7 @@ describe('storage manager', function () {
|
||||
})
|
||||
|
||||
it('ephemeral session should not persist data', async function () {
|
||||
this.retries(2)
|
||||
await Factory.registerUserToApplication({
|
||||
application: this.application,
|
||||
email: this.email,
|
||||
|
||||
@@ -885,6 +885,38 @@ describe('online conflict handling', function () {
|
||||
await this.sharedFinalAssertions()
|
||||
})
|
||||
|
||||
it('conflict where server updated_at_timestamp is less than base updated_at should not result in infinite loop', async function () {
|
||||
/**
|
||||
* While this shouldn't happen, I've seen this happen locally where a single UserPrefs object has a timestamp of A
|
||||
* on the server, and A + 10 on the client side. Somehow the client had a newer timestamp than the server. The
|
||||
* server rejects any change if the timestamp is not exactly equal. When we use the KeepBase strategy during conflict
|
||||
* resolution, we keep the base item, but give it the timestamp of the server item, so that the server accepts it.
|
||||
* However, RemoteDataConflict would only take the server's timestamp if it was greater than the base's timestamp.
|
||||
* Because this was not the case, the client kept sending up its own base timestamp and the server kept rejecting it,
|
||||
* and it never resolved. The fix made here was to take the server's timestamp no matter what, even if it is less than client's.
|
||||
*/
|
||||
const note = await Factory.createSyncedNote(this.application)
|
||||
this.expectedItemCount++
|
||||
|
||||
/** First modify the item without saving so that our local contents digress from the server's */
|
||||
await this.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = `${Math.random()}`
|
||||
})
|
||||
const modified = note.payload.copy({
|
||||
updated_at_timestamp: note.payload.updated_at_timestamp + 1,
|
||||
content: {
|
||||
...note.content,
|
||||
title: Math.random(),
|
||||
},
|
||||
dirty: true,
|
||||
})
|
||||
this.expectedItemCount++
|
||||
await this.application.itemManager.emitItemFromPayload(modified)
|
||||
await this.application.sync.sync()
|
||||
expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2)
|
||||
await this.sharedFinalAssertions()
|
||||
})
|
||||
|
||||
it('conflicting should not over resolve', async function () {
|
||||
/**
|
||||
* Before refactoring to use dirtyIndex instead of dirtiedDate, sometimes an item could be dirtied
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.151.2",
|
||||
"version": "2.151.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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.3.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/toast",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.11",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
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.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* keyboard shortcuts for primary actions ([#2030](https://github.com/standardnotes/app/issues/2030)) ([f49ba6b](https://github.com/standardnotes/app/commit/f49ba6bd4d38e4026bd88630014126a6e0086351))
|
||||
|
||||
## [1.13.18](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.13.17](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.13.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* distinguish client controlled features so that server expiration timestamps are ignored ([#2022](https://github.com/standardnotes/app/issues/2022)) ([a6e57e3](https://github.com/standardnotes/app/commit/a6e57e30cff64de83eaedf3d36117a497a7a70ae))
|
||||
|
||||
## [1.13.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.13.15",
|
||||
"version": "1.14.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
|
||||
export enum KeyboardKey {
|
||||
Tab = 'Tab',
|
||||
Backspace = 'Backspace',
|
||||
Up = 'ArrowUp',
|
||||
Down = 'ArrowDown',
|
||||
Left = 'ArrowLeft',
|
||||
Right = 'ArrowRight',
|
||||
Enter = 'Enter',
|
||||
Escape = 'Escape',
|
||||
Home = 'Home',
|
||||
End = 'End',
|
||||
}
|
||||
|
||||
export enum KeyboardModifier {
|
||||
Shift = 'Shift',
|
||||
Ctrl = 'Control',
|
||||
/** ⌘ key on Mac, ⊞ key on Windows */
|
||||
Meta = 'Meta',
|
||||
Alt = 'Alt',
|
||||
}
|
||||
|
||||
enum KeyboardKeyEvent {
|
||||
Down = 'KeyEventDown',
|
||||
Up = 'KeyEventUp',
|
||||
}
|
||||
|
||||
type KeyboardObserver = {
|
||||
key?: KeyboardKey | string
|
||||
modifiers?: KeyboardModifier[]
|
||||
onKeyDown?: (event: KeyboardEvent) => void
|
||||
onKeyUp?: (event: KeyboardEvent) => void
|
||||
element?: HTMLElement
|
||||
elements?: HTMLElement[]
|
||||
notElement?: HTMLElement
|
||||
notElementIds?: string[]
|
||||
notTags?: string[]
|
||||
}
|
||||
|
||||
export class IOService {
|
||||
readonly activeModifiers = new Set<KeyboardModifier>()
|
||||
private observers: KeyboardObserver[] = []
|
||||
|
||||
constructor(private isMac: boolean) {
|
||||
window.addEventListener('keydown', this.handleKeyDown)
|
||||
window.addEventListener('keyup', this.handleKeyUp)
|
||||
window.addEventListener('blur', this.handleWindowBlur)
|
||||
}
|
||||
|
||||
public deinit() {
|
||||
this.observers.length = 0
|
||||
window.removeEventListener('keydown', this.handleKeyDown)
|
||||
window.removeEventListener('keyup', this.handleKeyUp)
|
||||
window.removeEventListener('blur', this.handleWindowBlur)
|
||||
;(this.handleKeyDown as unknown) = undefined
|
||||
;(this.handleKeyUp as unknown) = undefined
|
||||
;(this.handleWindowBlur as unknown) = undefined
|
||||
}
|
||||
|
||||
private addActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||
if (!modifier) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (modifier) {
|
||||
case KeyboardModifier.Meta: {
|
||||
if (this.isMac) {
|
||||
this.activeModifiers.add(modifier)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardModifier.Ctrl: {
|
||||
if (!this.isMac) {
|
||||
this.activeModifiers.add(modifier)
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
this.activeModifiers.add(modifier)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||
if (!modifier) {
|
||||
return
|
||||
}
|
||||
|
||||
this.activeModifiers.delete(modifier)
|
||||
}
|
||||
|
||||
public cancelAllKeyboardModifiers = (): void => {
|
||||
this.activeModifiers.clear()
|
||||
}
|
||||
|
||||
public handleComponentKeyDown = (modifier: KeyboardModifier | undefined): void => {
|
||||
this.addActiveModifier(modifier)
|
||||
}
|
||||
|
||||
public handleComponentKeyUp = (modifier: KeyboardModifier | undefined): void => {
|
||||
this.removeActiveModifier(modifier)
|
||||
}
|
||||
|
||||
private handleKeyDown = (event: KeyboardEvent): void => {
|
||||
this.updateAllModifiersFromEvent(event)
|
||||
this.notifyObserver(event, KeyboardKeyEvent.Down)
|
||||
}
|
||||
|
||||
private handleKeyUp = (event: KeyboardEvent): void => {
|
||||
this.updateAllModifiersFromEvent(event)
|
||||
this.notifyObserver(event, KeyboardKeyEvent.Up)
|
||||
}
|
||||
|
||||
private updateAllModifiersFromEvent(event: KeyboardEvent): void {
|
||||
for (const modifier of Object.values(KeyboardModifier)) {
|
||||
if (event.getModifierState(modifier)) {
|
||||
this.addActiveModifier(modifier)
|
||||
} else {
|
||||
this.removeActiveModifier(modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowBlur = (): void => {
|
||||
for (const modifier of this.activeModifiers) {
|
||||
this.activeModifiers.delete(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
modifiersForEvent(event: KeyboardEvent): KeyboardModifier[] {
|
||||
const allModifiers = Object.values(KeyboardModifier)
|
||||
const eventModifiers = allModifiers.filter((modifier) => {
|
||||
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
||||
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
||||
const matches =
|
||||
((event.ctrlKey || event.key === KeyboardModifier.Ctrl) && modifier === KeyboardModifier.Ctrl) ||
|
||||
((event.metaKey || event.key === KeyboardModifier.Meta) && modifier === KeyboardModifier.Meta) ||
|
||||
((event.altKey || event.key === KeyboardModifier.Alt) && modifier === KeyboardModifier.Alt) ||
|
||||
((event.shiftKey || event.key === KeyboardModifier.Shift) && modifier === KeyboardModifier.Shift)
|
||||
|
||||
return matches
|
||||
})
|
||||
|
||||
return eventModifiers
|
||||
}
|
||||
|
||||
private eventMatchesKeyAndModifiers(
|
||||
event: KeyboardEvent,
|
||||
key: KeyboardKey | string | undefined,
|
||||
modifiers: KeyboardModifier[] = [],
|
||||
): boolean {
|
||||
const eventModifiers = this.modifiersForEvent(event)
|
||||
if (eventModifiers.length !== modifiers.length) {
|
||||
return false
|
||||
}
|
||||
for (const modifier of modifiers) {
|
||||
if (!eventModifiers.includes(modifier)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Modifers match, check key
|
||||
if (!key) {
|
||||
return true
|
||||
}
|
||||
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
|
||||
// In our case we don't differentiate between the two.
|
||||
return key.toLowerCase() === event.key.toLowerCase()
|
||||
}
|
||||
|
||||
notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
||||
const target = event.target as HTMLElement
|
||||
for (const observer of this.observers) {
|
||||
if (observer.element && event.target !== observer.element) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.elements && !observer.elements.includes(target)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notElement && observer.notElement === event.target) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notTags && observer.notTags.includes(target.tagName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
|
||||
const callback = keyEvent === KeyboardKeyEvent.Down ? observer.onKeyDown : observer.onKeyUp
|
||||
if (callback) {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addKeyObserver(observer: KeyboardObserver): () => void {
|
||||
this.observers.push(observer)
|
||||
|
||||
const thislessObservers = this.observers
|
||||
return () => {
|
||||
removeFromArray(thislessObservers, observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { KeyboardCommand } from './KeyboardCommands'
|
||||
|
||||
export type KeyboardCommandHandler = {
|
||||
command: KeyboardCommand
|
||||
onKeyDown?: (event: KeyboardEvent) => boolean | void
|
||||
onKeyUp?: (event: KeyboardEvent) => boolean | void
|
||||
element?: HTMLElement
|
||||
elements?: HTMLElement[]
|
||||
notElement?: HTMLElement
|
||||
notElementIds?: string[]
|
||||
notTags?: string[]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export type KeyboardCommand = symbol
|
||||
|
||||
function createKeyboardCommand(type: string): KeyboardCommand {
|
||||
return Symbol(type)
|
||||
}
|
||||
|
||||
export const TOGGLE_LIST_PANE_KEYBOARD_COMMAND = createKeyboardCommand('TOGGLE_LIST_PANE_KEYBOARD_COMMAND')
|
||||
export const TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND = createKeyboardCommand('TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND')
|
||||
export const CREATE_NEW_NOTE_KEYBOARD_COMMAND = createKeyboardCommand('CREATE_NEW_NOTE_KEYBOARD_COMMAND')
|
||||
export const NEXT_LIST_ITEM_KEYBOARD_COMMAND = createKeyboardCommand('NEXT_LIST_ITEM_KEYBOARD_COMMAND')
|
||||
export const PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND = createKeyboardCommand('PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND')
|
||||
export const SEARCH_KEYBOARD_COMMAND = createKeyboardCommand('SEARCH_KEYBOARD_COMMAND')
|
||||
export const CANCEL_SEARCH_COMMAND = createKeyboardCommand('CANCEL_SEARCH_COMMAND')
|
||||
export const SELECT_ALL_ITEMS_KEYBOARD_COMMAND = createKeyboardCommand('SELECT_ALL_ITEMS_KEYBOARD_COMMAND')
|
||||
export const SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND = createKeyboardCommand('SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND')
|
||||
export const DELETE_NOTE_KEYBOARD_COMMAND = createKeyboardCommand('DELETE_NOTE_KEYBOARD_COMMAND')
|
||||
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 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')
|
||||
export const OPEN_NOTE_HISTORY_COMMAND = createKeyboardCommand('OPEN_NOTE_HISTORY_COMMAND')
|
||||
export const CAPTURE_SAVE_COMMAND = createKeyboardCommand('CAPTURE_SAVE_COMMAND')
|
||||
export const STAR_NOTE_COMMAND = createKeyboardCommand('STAR_NOTE_COMMAND')
|
||||
export const PIN_NOTE_COMMAND = createKeyboardCommand('PIN_NOTE_COMMAND')
|
||||
@@ -0,0 +1,12 @@
|
||||
export enum KeyboardKey {
|
||||
Tab = 'Tab',
|
||||
Backspace = 'Backspace',
|
||||
Up = 'ArrowUp',
|
||||
Down = 'ArrowDown',
|
||||
Left = 'ArrowLeft',
|
||||
Right = 'ArrowRight',
|
||||
Enter = 'Enter',
|
||||
Escape = 'Escape',
|
||||
Home = 'Home',
|
||||
End = 'End',
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum KeyboardKeyEvent {
|
||||
Down = 'KeyEventDown',
|
||||
Up = 'KeyEventUp',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum KeyboardModifier {
|
||||
Shift = 'Shift',
|
||||
Ctrl = 'Control',
|
||||
/** ⌘ key on Mac, ⊞ key on Windows */
|
||||
Meta = 'Meta',
|
||||
Alt = 'Alt',
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Environment, Platform } from '@standardnotes/snjs'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { eventMatchesKeyAndModifiers } from './eventMatchesKeyAndModifiers'
|
||||
import { KeyboardCommand } from './KeyboardCommands'
|
||||
import { KeyboardKeyEvent } from './KeyboardKeyEvent'
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
import { KeyboardCommandHandler } from './KeyboardCommandHandler'
|
||||
import { KeyboardShortcut, PlatformedKeyboardShortcut } from './KeyboardShortcut'
|
||||
import { getKeyboardShortcuts } from './getKeyboardShortcuts'
|
||||
|
||||
export class KeyboardService {
|
||||
readonly activeModifiers = new Set<KeyboardModifier>()
|
||||
private commandHandlers: KeyboardCommandHandler[] = []
|
||||
private commandMap = new Map<KeyboardCommand, KeyboardShortcut>()
|
||||
|
||||
constructor(private platform: Platform, environment: Environment) {
|
||||
window.addEventListener('keydown', this.handleKeyDown)
|
||||
window.addEventListener('keyup', this.handleKeyUp)
|
||||
window.addEventListener('blur', this.handleWindowBlur)
|
||||
|
||||
const shortcuts = getKeyboardShortcuts(platform, environment)
|
||||
for (const shortcut of shortcuts) {
|
||||
this.registerShortcut(shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
get isMac() {
|
||||
return this.platform === Platform.MacDesktop || this.platform === Platform.MacWeb
|
||||
}
|
||||
|
||||
public deinit() {
|
||||
this.commandHandlers.length = 0
|
||||
window.removeEventListener('keydown', this.handleKeyDown)
|
||||
window.removeEventListener('keyup', this.handleKeyUp)
|
||||
window.removeEventListener('blur', this.handleWindowBlur)
|
||||
;(this.handleKeyDown as unknown) = undefined
|
||||
;(this.handleKeyUp as unknown) = undefined
|
||||
;(this.handleWindowBlur as unknown) = undefined
|
||||
}
|
||||
|
||||
private addActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||
if (!modifier) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (modifier) {
|
||||
case KeyboardModifier.Meta: {
|
||||
if (this.isMac) {
|
||||
this.activeModifiers.add(modifier)
|
||||
}
|
||||
break
|
||||
}
|
||||
case KeyboardModifier.Ctrl: {
|
||||
if (!this.isMac) {
|
||||
this.activeModifiers.add(modifier)
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
this.activeModifiers.add(modifier)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||
if (!modifier) {
|
||||
return
|
||||
}
|
||||
|
||||
this.activeModifiers.delete(modifier)
|
||||
}
|
||||
|
||||
public cancelAllKeyboardModifiers = (): void => {
|
||||
this.activeModifiers.clear()
|
||||
}
|
||||
|
||||
public handleComponentKeyDown = (modifier: KeyboardModifier | undefined): void => {
|
||||
this.addActiveModifier(modifier)
|
||||
}
|
||||
|
||||
public handleComponentKeyUp = (modifier: KeyboardModifier | undefined): void => {
|
||||
this.removeActiveModifier(modifier)
|
||||
}
|
||||
|
||||
private handleKeyDown = (event: KeyboardEvent): void => {
|
||||
this.updateAllModifiersFromEvent(event)
|
||||
|
||||
this.handleKeyboardEvent(event, KeyboardKeyEvent.Down)
|
||||
}
|
||||
|
||||
private handleKeyUp = (event: KeyboardEvent): void => {
|
||||
this.updateAllModifiersFromEvent(event)
|
||||
|
||||
this.handleKeyboardEvent(event, KeyboardKeyEvent.Up)
|
||||
}
|
||||
|
||||
private updateAllModifiersFromEvent(event: KeyboardEvent): void {
|
||||
for (const modifier of Object.values(KeyboardModifier)) {
|
||||
if (event.getModifierState(modifier)) {
|
||||
this.addActiveModifier(modifier)
|
||||
} else {
|
||||
this.removeActiveModifier(modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowBlur = (): void => {
|
||||
for (const modifier of this.activeModifiers) {
|
||||
this.activeModifiers.delete(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyboardEvent(event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
||||
for (const command of this.commandMap.keys()) {
|
||||
const shortcut = this.commandMap.get(command)
|
||||
if (!shortcut) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (eventMatchesKeyAndModifiers(event, shortcut)) {
|
||||
if (shortcut.preventDefault) {
|
||||
event.preventDefault()
|
||||
}
|
||||
this.handleCommand(command, event, keyEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleCommand(command: KeyboardCommand, event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
||||
const target = event.target as HTMLElement
|
||||
|
||||
for (const observer of this.commandHandlers) {
|
||||
if (observer.command !== command) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.element && event.target !== observer.element) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.elements && !observer.elements.includes(target)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notElement && observer.notElement === event.target) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (observer.notTags && observer.notTags.includes(target.tagName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const callback = keyEvent === KeyboardKeyEvent.Down ? observer.onKeyDown : observer.onKeyUp
|
||||
if (callback) {
|
||||
const exclusive = callback(event)
|
||||
if (exclusive) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerShortcut(shortcut: KeyboardShortcut): void {
|
||||
this.commandMap.set(shortcut.command, shortcut)
|
||||
}
|
||||
|
||||
addCommandHandler(observer: KeyboardCommandHandler): () => void {
|
||||
this.commandHandlers.push(observer)
|
||||
|
||||
const thislessObservers = this.commandHandlers
|
||||
return () => {
|
||||
observer.onKeyDown = undefined
|
||||
observer.onKeyDown = undefined
|
||||
removeFromArray(thislessObservers, observer)
|
||||
}
|
||||
}
|
||||
|
||||
addCommandHandlers(handlers: KeyboardCommandHandler[]): () => void {
|
||||
const disposers = handlers.map((handler) => this.addCommandHandler(handler))
|
||||
return () => {
|
||||
for (const disposer of disposers) {
|
||||
disposer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyboardShortcutForCommand(command: KeyboardCommand): PlatformedKeyboardShortcut | undefined {
|
||||
const shortcut = this.commandMap.get(command)
|
||||
if (!shortcut) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
platform: this.platform,
|
||||
...shortcut,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Platform } from '@standardnotes/snjs'
|
||||
import { KeyboardCommand } from './KeyboardCommands'
|
||||
import { KeyboardKey } from './KeyboardKey'
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
|
||||
export type KeyboardShortcut = {
|
||||
command: KeyboardCommand
|
||||
modifiers?: KeyboardModifier[]
|
||||
key?: KeyboardKey | string
|
||||
/**
|
||||
* Alternative to using key, if the key can be affected by alt + shift. For example, if you want alt + shift + n,
|
||||
* use code 'KeyN' instead of key 'n', as the modifiers would turn n into '˜' on Mac.
|
||||
*/
|
||||
code?: string
|
||||
preventDefault?: boolean
|
||||
}
|
||||
|
||||
export type PlatformedKeyboardShortcut = KeyboardShortcut & {
|
||||
platform: Platform
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { KeyboardShortcut } from './KeyboardShortcut'
|
||||
import { modifiersForEvent } from './modifiersForEvent'
|
||||
|
||||
export function eventMatchesKeyAndModifiers(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {
|
||||
const eventModifiers = modifiersForEvent(event)
|
||||
const shortcutModifiers = shortcut.modifiers ?? []
|
||||
|
||||
if (eventModifiers.length !== shortcutModifiers.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const modifier of shortcutModifiers) {
|
||||
if (!eventModifiers.includes(modifier)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!shortcut.key && !shortcut.code) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (shortcut.key) {
|
||||
return shortcut.key.toLowerCase() === event.key.toLowerCase()
|
||||
} else {
|
||||
return shortcut.code === event.code
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Environment, Platform } from '@standardnotes/snjs'
|
||||
import { isMacPlatform } from './platformCheck'
|
||||
import {
|
||||
CREATE_NEW_NOTE_KEYBOARD_COMMAND,
|
||||
TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
|
||||
TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
|
||||
NEXT_LIST_ITEM_KEYBOARD_COMMAND,
|
||||
PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||
SEARCH_KEYBOARD_COMMAND,
|
||||
SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
|
||||
SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||
DELETE_NOTE_KEYBOARD_COMMAND,
|
||||
TAB_COMMAND,
|
||||
ESCAPE_COMMAND,
|
||||
CANCEL_SEARCH_COMMAND,
|
||||
TOGGLE_FOCUS_MODE_COMMAND,
|
||||
CHANGE_EDITOR_COMMAND,
|
||||
FOCUS_TAGS_INPUT_COMMAND,
|
||||
CREATE_NEW_TAG_COMMAND,
|
||||
OPEN_NOTE_HISTORY_COMMAND,
|
||||
CAPTURE_SAVE_COMMAND,
|
||||
STAR_NOTE_COMMAND,
|
||||
PIN_NOTE_COMMAND,
|
||||
} from './KeyboardCommands'
|
||||
import { KeyboardKey } from './KeyboardKey'
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
import { KeyboardShortcut } from './KeyboardShortcut'
|
||||
|
||||
export function getKeyboardShortcuts(platform: Platform, _environment: Environment): KeyboardShortcut[] {
|
||||
const isMac = isMacPlatform(platform)
|
||||
|
||||
const primaryModifier = isMac ? KeyboardModifier.Meta : KeyboardModifier.Ctrl
|
||||
|
||||
return [
|
||||
{
|
||||
command: TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
|
||||
key: 'l',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
|
||||
key: 'e',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
|
||||
code: 'KeyN',
|
||||
modifiers: [KeyboardModifier.Alt, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
|
||||
key: KeyboardKey.Down,
|
||||
},
|
||||
{
|
||||
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||
key: KeyboardKey.Up,
|
||||
},
|
||||
{
|
||||
command: SEARCH_KEYBOARD_COMMAND,
|
||||
code: 'KeyF',
|
||||
modifiers: [KeyboardModifier.Alt, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: CANCEL_SEARCH_COMMAND,
|
||||
key: KeyboardKey.Escape,
|
||||
},
|
||||
{
|
||||
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
|
||||
key: 'a',
|
||||
modifiers: [primaryModifier],
|
||||
},
|
||||
{
|
||||
command: SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||
modifiers: [KeyboardModifier.Alt],
|
||||
},
|
||||
{
|
||||
command: DELETE_NOTE_KEYBOARD_COMMAND,
|
||||
key: KeyboardKey.Backspace,
|
||||
modifiers: [primaryModifier],
|
||||
},
|
||||
{
|
||||
command: TAB_COMMAND,
|
||||
key: KeyboardKey.Tab,
|
||||
},
|
||||
{
|
||||
command: ESCAPE_COMMAND,
|
||||
key: KeyboardKey.Escape,
|
||||
},
|
||||
{
|
||||
command: TOGGLE_FOCUS_MODE_COMMAND,
|
||||
key: 'f',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
},
|
||||
{
|
||||
command: CHANGE_EDITOR_COMMAND,
|
||||
key: '/',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: FOCUS_TAGS_INPUT_COMMAND,
|
||||
code: 'KeyT',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Alt],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: CREATE_NEW_TAG_COMMAND,
|
||||
code: 'KeyN',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Alt],
|
||||
},
|
||||
{
|
||||
command: OPEN_NOTE_HISTORY_COMMAND,
|
||||
key: 'h',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: CAPTURE_SAVE_COMMAND,
|
||||
key: 's',
|
||||
modifiers: [primaryModifier],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: STAR_NOTE_COMMAND,
|
||||
key: 's',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
{
|
||||
command: PIN_NOTE_COMMAND,
|
||||
key: 'p',
|
||||
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||
preventDefault: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Platform } from '@standardnotes/snjs'
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
|
||||
function isMacPlatform(platform: Platform) {
|
||||
return platform === Platform.MacDesktop || platform === Platform.MacWeb
|
||||
}
|
||||
|
||||
export function keyboardCharacterForModifier(modifier: KeyboardModifier, platform: Platform) {
|
||||
const isMac = isMacPlatform(platform)
|
||||
|
||||
if (modifier === KeyboardModifier.Meta) {
|
||||
return isMac ? '⌘' : '⊞'
|
||||
} else if (modifier === KeyboardModifier.Ctrl) {
|
||||
return isMac ? '⌃' : 'Ctrl'
|
||||
} else if (modifier === KeyboardModifier.Alt) {
|
||||
return isMac ? '⌥' : 'Alt'
|
||||
} else if (modifier === KeyboardModifier.Shift) {
|
||||
return isMac ? '⇧' : 'Shift'
|
||||
} else {
|
||||
return KeyboardModifier[modifier]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { isMacPlatform } from '@standardnotes/ui-services'
|
||||
import { keyboardCharacterForModifier } from './keyboardCharacterForModifier'
|
||||
import { PlatformedKeyboardShortcut } from './KeyboardShortcut'
|
||||
|
||||
function stringForCode(code = ''): string {
|
||||
return code.replace('Key', '').replace('Digit', '')
|
||||
}
|
||||
|
||||
export function keyboardStringForShortcut(shortcut: PlatformedKeyboardShortcut | undefined) {
|
||||
if (!shortcut) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const key = shortcut.key?.toUpperCase() || stringForCode(shortcut.code)
|
||||
|
||||
if (!shortcut.modifiers || shortcut.modifiers.length === 0) {
|
||||
return key
|
||||
}
|
||||
|
||||
const modifierCharacters = shortcut.modifiers.map((modifier) =>
|
||||
keyboardCharacterForModifier(modifier, shortcut.platform),
|
||||
)
|
||||
|
||||
if (isMacPlatform(shortcut.platform)) {
|
||||
return `${modifierCharacters.join('')}${key}`
|
||||
} else {
|
||||
return `${modifierCharacters.join('+')}+${key}`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { KeyboardModifier } from './KeyboardModifier'
|
||||
|
||||
export function modifiersForEvent(event: KeyboardEvent): KeyboardModifier[] {
|
||||
const allModifiers = Object.values(KeyboardModifier)
|
||||
const eventModifiers = allModifiers.filter((modifier) => {
|
||||
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
||||
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
||||
const matches =
|
||||
((event.ctrlKey || event.key === KeyboardModifier.Ctrl) && modifier === KeyboardModifier.Ctrl) ||
|
||||
((event.metaKey || event.key === KeyboardModifier.Meta) && modifier === KeyboardModifier.Meta) ||
|
||||
((event.altKey || event.key === KeyboardModifier.Alt) && modifier === KeyboardModifier.Alt) ||
|
||||
((event.shiftKey || event.key === KeyboardModifier.Shift) && modifier === KeyboardModifier.Shift)
|
||||
|
||||
return matches
|
||||
})
|
||||
|
||||
return eventModifiers
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Platform } from '@standardnotes/snjs'
|
||||
|
||||
export function isMacPlatform(platform: Platform) {
|
||||
return platform === Platform.MacDesktop || platform === Platform.MacWeb
|
||||
}
|
||||
|
||||
export function isWindowsPlatform(platform: Platform) {
|
||||
return platform === Platform.WindowsDesktop || platform === Platform.WindowsWeb
|
||||
}
|
||||
@@ -153,21 +153,24 @@ export class ThemeManager extends AbstractService {
|
||||
|
||||
private handleFeaturesUpdated(): void {
|
||||
let hasChange = false
|
||||
|
||||
for (const themeUuid of this.activeThemes) {
|
||||
const theme = this.application.items.findItem(themeUuid) as SNTheme
|
||||
|
||||
if (!theme) {
|
||||
this.deactivateTheme(themeUuid)
|
||||
hasChange = true
|
||||
} else {
|
||||
const status = this.application.features.getFeatureStatus(theme.identifier)
|
||||
if (status !== FeatureStatus.Entitled) {
|
||||
if (theme.active) {
|
||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||
} else {
|
||||
this.deactivateTheme(theme.uuid)
|
||||
}
|
||||
hasChange = true
|
||||
continue
|
||||
}
|
||||
|
||||
const status = this.application.features.getFeatureStatus(theme.identifier)
|
||||
if (status !== FeatureStatus.Entitled) {
|
||||
if (theme.active) {
|
||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||
} else {
|
||||
this.deactivateTheme(theme.uuid)
|
||||
}
|
||||
hasChange = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
export * from './Alert/Functions'
|
||||
export * from './Alert/WebAlertService'
|
||||
export * from './Archive/ArchiveManager'
|
||||
export * from './IO/IOService'
|
||||
export * from './Keyboard/KeyboardService'
|
||||
export * from './Keyboard/KeyboardShortcut'
|
||||
export * from './Keyboard/KeyboardCommands'
|
||||
export * from './Keyboard/platformCheck'
|
||||
export * from './Keyboard/KeyboardKey'
|
||||
export * from './Keyboard/KeyboardModifier'
|
||||
export * from './Keyboard/keyboardCharacterForModifier'
|
||||
export * from './Keyboard/keyboardStringForShortcut'
|
||||
export * from './Preferences/PreferenceId'
|
||||
export * from './Route/Params/DemoParams'
|
||||
export * from './Route/Params/OnboardingParams'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,83 @@
|
||||
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
|
||||
|
||||
* improved error reporting in super; improved draggable block icon padding ([24ff87c](https://github.com/standardnotes/app/commit/24ff87c30625432393ffedeada14478ebb625292))
|
||||
|
||||
## [3.103.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue where converting empty plain note to super would result in error ([e58c563](https://github.com/standardnotes/app/commit/e58c5637951f928efcd8c8427f06d0b6c69b5e55))
|
||||
|
||||
# [3.103.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **web:** enable block drag'n'drop in super editor ([#2029](https://github.com/standardnotes/app/issues/2029)) ([dab4f67](https://github.com/standardnotes/app/commit/dab4f678f04aee2c7dfbe7f416e7fc34e8d870cf))
|
||||
|
||||
# [3.102.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
### Features
|
||||
|
||||
* keyboard shortcuts for primary actions ([#2030](https://github.com/standardnotes/app/issues/2030)) ([f49ba6b](https://github.com/standardnotes/app/commit/f49ba6bd4d38e4026bd88630014126a6e0086351))
|
||||
|
||||
## [3.101.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.101.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.101.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Features
|
||||
|
||||
* click saving indicator to toggle visibility ([#2028](https://github.com/standardnotes/app/issues/2028)) ([f793673](https://github.com/standardnotes/app/commit/f7936738759d4c2aa9d5a4a370649a20d758d3f9))
|
||||
|
||||
## [3.100.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* select first item when switching tags ([#2026](https://github.com/standardnotes/app/issues/2026)) ([b9e6dc4](https://github.com/standardnotes/app/commit/b9e6dc4c2beafed0562fa41f6cb05c987fdc2e08))
|
||||
|
||||
## [3.100.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.100.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.100.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.100.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-11-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.100.4",
|
||||
"version": "3.105.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
@@ -99,9 +99,6 @@
|
||||
"prettier-plugin-tailwindcss": "^0.1.13",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-touch-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-refresh": "^0.14.0",
|
||||
"sass-loader": "*",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
--sn-stylekit-accessory-tint-color-1: #941648;
|
||||
|
||||
--sn-stylekit-shadow-color: var(--background-2);
|
||||
--sn-stylekit-shadow-color: #c4b89f;
|
||||
--sn-stylekit-background-color: var(--background-1);
|
||||
--sn-stylekit-foreground-color: var(--foreground-color);
|
||||
--sn-stylekit-border-color: var(--border-color);
|
||||
|
||||
@@ -29,7 +29,8 @@ import { DesktopManager } from './Device/DesktopManager'
|
||||
import {
|
||||
ArchiveManager,
|
||||
AutolockService,
|
||||
IOService,
|
||||
KeyboardService,
|
||||
PreferenceId,
|
||||
RouteService,
|
||||
RouteServiceInterface,
|
||||
ThemeManager,
|
||||
@@ -85,19 +86,17 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
this.itemControllerGroup = new ItemGroupController(this)
|
||||
this.routeService = new RouteService(this, internalEventBus)
|
||||
|
||||
const viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
||||
const archiveService = new ArchiveManager(this)
|
||||
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
||||
const themeService = new ThemeManager(this, internalEventBus)
|
||||
|
||||
this.setWebServices({
|
||||
viewControllerManager,
|
||||
archiveService,
|
||||
desktopService: isDesktopDevice(deviceInterface) ? new DesktopManager(this, deviceInterface) : undefined,
|
||||
io,
|
||||
autolockService: this.isNativeMobileWeb() ? undefined : new AutolockService(this, internalEventBus),
|
||||
themeService,
|
||||
})
|
||||
this.webServices = {} as WebServices
|
||||
this.webServices.keyboardService = new KeyboardService(platform, this.environment)
|
||||
this.webServices.archiveService = new ArchiveManager(this)
|
||||
this.webServices.themeService = new ThemeManager(this, internalEventBus)
|
||||
this.webServices.autolockService = this.isNativeMobileWeb()
|
||||
? undefined
|
||||
: new AutolockService(this, internalEventBus)
|
||||
this.webServices.desktopService = isDesktopDevice(deviceInterface)
|
||||
? new DesktopManager(this, deviceInterface)
|
||||
: undefined
|
||||
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
||||
|
||||
if (this.isNativeMobileWeb()) {
|
||||
this.mobileWebReceiver = new MobileWebReceiver(this)
|
||||
@@ -152,10 +151,6 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
}
|
||||
}
|
||||
|
||||
setWebServices(services: WebServices): void {
|
||||
this.webServices = services
|
||||
}
|
||||
|
||||
public addWebEventObserver(observer: WebEventObserver): () => void {
|
||||
this.webEventObservers.push(observer)
|
||||
|
||||
@@ -194,6 +189,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return this.webServices.archiveService
|
||||
}
|
||||
|
||||
public get paneController() {
|
||||
return this.webServices.viewControllerManager.paneController
|
||||
}
|
||||
|
||||
public get desktopDevice(): DesktopDeviceInterface | undefined {
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
return this.deviceInterface
|
||||
@@ -221,8 +220,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return this.webServices.themeService
|
||||
}
|
||||
|
||||
public get io() {
|
||||
return this.webServices.io
|
||||
public get keyboardService() {
|
||||
return this.webServices.keyboardService
|
||||
}
|
||||
|
||||
async checkForSecurityUpdate() {
|
||||
@@ -389,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user