Merge branch 'develop' into feature/account-menu-react
This commit is contained in:
22
README.md
22
README.md
@@ -3,13 +3,13 @@
|
||||
|
||||
Standard Notes is a simple and private notes app available on most platforms, including Web, Mac, Windows, Linux, iOS, and Android. It focuses on simplicity, and encrypts data locally before it ever touches a cloud. This means no one can read your notes but you (not even us).
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/standardnotes/desktop/releases)
|
||||
[](https://github.com/standardnotes/web/blob/main/LICENSE)
|
||||
[](https://standardnotes.org/slack)
|
||||
[](https://standardnotes.com/slack)
|
||||
[](https://twitter.com/standardnotes)
|
||||
|
||||
</div>
|
||||
@@ -19,25 +19,25 @@ Standard Notes is a simple and private notes app available on most platforms, in
|
||||
- Simple and easy to use
|
||||
- Fast and encrypted cross-platform sync
|
||||
- Free sync on unlimited devices
|
||||
- Extensible with editors (such as Markdown and Code), themes, and components (like Folders and Autocomplete Tags). Learn more about [Extended](https://standardnotes.org/extensions).
|
||||
- Extensible with editors (such as Markdown and Code), themes, and components (like Folders and Autocomplete Tags). Learn more about [Extended](https://standardnotes.com/extensions).
|
||||
- Open-source and the option to self-host your notes server. You can [host your own Standard Server](https://docs.standardnotes.org/self-hosting/getting-started) in a few easy steps.
|
||||
- A strong focus on longevity and sustainability. [Learn more](https://standardnotes.org/longevity).
|
||||
- A strong focus on longevity and sustainability. [Learn more](https://standardnotes.com/longevity).
|
||||
|
||||
### Creating your private notes account
|
||||
|
||||
1. Launch the web app at [app.standardnotes.org](https://app.standardnotes.org).
|
||||
2. Click Register to create your private notes account.
|
||||
3. Download Standard Notes on all your devices.
|
||||
- [Mac](https://standardnotes.org/download/mac)
|
||||
- [Windows](https://standardnotes.org/download/windows)
|
||||
- [Linux](https://standardnotes.org/download/linux)
|
||||
- [iOS](https://standardnotes.org/download/https://itunes.apple.com/us/app/standard-notes/id1285392450?mt=8)
|
||||
- [Mac](https://standardnotes.com/download/mac)
|
||||
- [Windows](https://standardnotes.com/download/windows)
|
||||
- [Linux](https://standardnotes.com/download/linux)
|
||||
- [iOS](https://standardnotes.com/download/https://itunes.apple.com/us/app/standard-notes/id1285392450?mt=8)
|
||||
- [Android](https://play.google.com/store/apps/details?id=com.standardnotes)
|
||||
4. You're all set to begin enjoying a new, more freeing notes life. Standard Notes comes out of the box with end-to-end encrypted sync on all your devices.
|
||||
|
||||
### Do More
|
||||
|
||||
If you're looking to power up your experience with extensions, and help support future development, [learn more about Extended](https://standardnotes.org/extensions). Extended offers:
|
||||
If you're looking to power up your experience with extensions, and help support future development, [learn more about Extended](https://standardnotes.com/extensions). Extended offers:
|
||||
|
||||
- Powerful editors, including the Plus Editor, Simple Markdown, Advanced Markdown, Code Editor, Vim Editor, and the popular Simple Task Editor.
|
||||
- Beautiful themes to help you find inspiration in any mood, like Midnight, Focused, Futura, Titanium, and Solarized Dark.
|
||||
@@ -52,11 +52,11 @@ It's no secret we love to write. Standard Notes has become a dependable environm
|
||||
|
||||
### Plug In
|
||||
|
||||
Plug in to the community of note-lovers and privacy-enthusiasts. Join us on [Slack](https://standardnotes.org/slack), on our [GitHub forum](https://forum.standardnotes.org), and follow new updates on [Twitter](https://twitter.com/StandardNotes).
|
||||
Plug in to the community of note-lovers and privacy-enthusiasts. Join us on [Slack](https://standardnotes.com/slack), on our [GitHub forum](https://forum.standardnotes.org), and follow new updates on [Twitter](https://twitter.com/StandardNotes).
|
||||
|
||||
Developers can create and publish their own extensions. Visit the [documentation hub](https://docs.standardnotes.org/) to learn more.
|
||||
|
||||
Questions? Find answers on our [Help page](https://standardnotes.org/help).
|
||||
Questions? Find answers on our [Help page](https://standardnotes.com/help).
|
||||
|
||||
🙏
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Thank you for your work in helping keep Standard Notes safe and secure. If you b
|
||||
|
||||
# In-scope
|
||||
|
||||
- Security issues in any current release of Standard Notes. Our product downloads are available on our homepage at https://standardnotes.org, and our source code is available at https://github.com/standardnotes.
|
||||
- Security issues in any current release of Standard Notes. Our product downloads are available on our homepage at https://standardnotes.com, and our source code is available at https://github.com/standardnotes.
|
||||
|
||||
# Exclusions
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ export const NoteTag = observer(({ appState, tag }: Props) => {
|
||||
ref={deleteTagRef}
|
||||
type="button"
|
||||
className="ml-2 -mr-1 border-0 p-0 bg-transparent cursor-pointer flex"
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={onDeleteTagClick}
|
||||
>
|
||||
|
||||
@@ -2,13 +2,19 @@ import { AppState } from '@/ui_models/app_state';
|
||||
import { toDirective, useCloseOnBlur, useCloseOnClickOutside } from './utils';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { NotesOptions } from './NotesOptions';
|
||||
import { useRef } from 'preact/hooks';
|
||||
import { useCallback, useEffect, useRef } from 'preact/hooks';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
};
|
||||
|
||||
const NotesContextMenu = observer(({ appState }: Props) => {
|
||||
const {
|
||||
contextMenuOpen,
|
||||
contextMenuPosition,
|
||||
contextMenuMaxHeight,
|
||||
} = appState.notes;
|
||||
|
||||
const contextMenuRef = useRef<HTMLDivElement>();
|
||||
const [closeOnBlur] = useCloseOnBlur(
|
||||
contextMenuRef,
|
||||
@@ -20,13 +26,24 @@ const NotesContextMenu = observer(({ appState }: Props) => {
|
||||
(open: boolean) => appState.notes.setContextMenuOpen(open)
|
||||
);
|
||||
|
||||
return appState.notes.contextMenuOpen ? (
|
||||
const reloadContextMenuLayout = useCallback(() => {
|
||||
appState.notes.reloadContextMenuLayout();
|
||||
}, [appState.notes]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', reloadContextMenuLayout);
|
||||
return () => {
|
||||
window.removeEventListener('resize', reloadContextMenuLayout);
|
||||
};
|
||||
}, [reloadContextMenuLayout]);
|
||||
|
||||
return contextMenuOpen ? (
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className="sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed"
|
||||
style={{
|
||||
...appState.notes.contextMenuPosition,
|
||||
maxHeight: appState.notes.contextMenuMaxHeight,
|
||||
...contextMenuPosition,
|
||||
maxHeight: contextMenuMaxHeight,
|
||||
}}
|
||||
>
|
||||
<NotesOptions appState={appState} closeOnBlur={closeOnBlur} />
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@reach/disclosure';
|
||||
import { Switch } from './Switch';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
@@ -31,6 +32,7 @@ const SearchOptions = observer(({ appState }: Props) => {
|
||||
top: 0,
|
||||
right: 0,
|
||||
});
|
||||
const [maxWidth, setMaxWidth] = useState<number | 'auto'>('auto');
|
||||
const buttonRef = useRef<HTMLButtonElement>();
|
||||
const panelRef = useRef<HTMLDivElement>();
|
||||
const [closeOnBlur, setLockCloseOnBlur] = useCloseOnBlur(panelRef, setOpen);
|
||||
@@ -44,15 +46,27 @@ const SearchOptions = observer(({ appState }: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateWidthAndPosition = () => {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
setMaxWidth(rect.right - 16);
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', updateWidthAndPosition);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateWidthAndPosition);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Disclosure
|
||||
open={open}
|
||||
onChange={() => {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
setPosition({
|
||||
top: rect.bottom,
|
||||
right: document.body.clientWidth - rect.right,
|
||||
});
|
||||
updateWidthAndPosition();
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
@@ -68,8 +82,9 @@ const SearchOptions = observer(({ appState }: Props) => {
|
||||
ref={panelRef}
|
||||
style={{
|
||||
...position,
|
||||
maxWidth,
|
||||
}}
|
||||
className="sn-dropdown sn-dropdown--animated min-w-80 fixed grid gap-2 py-2"
|
||||
className="sn-dropdown sn-dropdown--animated w-80 fixed grid gap-2 py-2"
|
||||
onBlur={closeOnBlur}
|
||||
>
|
||||
<Switch
|
||||
|
||||
@@ -41,14 +41,14 @@ function useSessions(
|
||||
(async () => {
|
||||
setRefreshing(true);
|
||||
const response = await application.getSessions();
|
||||
if ('error' in response || !response.data) {
|
||||
if ('error' in response || isNullOrUndefined(response.data)) {
|
||||
if (response.error?.message) {
|
||||
setErrorMessage(response.error.message);
|
||||
} else {
|
||||
setErrorMessage('An unknown error occured while loading sessions.');
|
||||
}
|
||||
} else {
|
||||
const sessions = response.data;
|
||||
const sessions = response.data as RemoteSession[];
|
||||
setSessions(sessions);
|
||||
setErrorMessage('');
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ export class NoteTagsState {
|
||||
|
||||
autocompleteTagHintVisible: computed,
|
||||
|
||||
clearAutocompleteSearch: action,
|
||||
focusNextTag: action,
|
||||
focusPreviousTag: action,
|
||||
setAutocompleteInputFocused: action,
|
||||
setAutocompleteSearchQuery: action,
|
||||
setAutocompleteTagHintFocused: action,
|
||||
@@ -41,7 +38,6 @@ export class NoteTagsState {
|
||||
setFocusedTagUuid: action,
|
||||
setTags: action,
|
||||
setTagsContainerMaxWidth: action,
|
||||
reloadTags: action,
|
||||
});
|
||||
|
||||
appEventListeners.push(
|
||||
|
||||
@@ -28,6 +28,7 @@ export class NotesState {
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
contextMenuClickLocation: { x: number, y: number } = { x: 0, y: 0 };
|
||||
contextMenuMaxHeight: number | 'auto' = 'auto';
|
||||
showProtectedWarning = false;
|
||||
|
||||
@@ -47,6 +48,7 @@ export class NotesState {
|
||||
trashedNotesCount: computed,
|
||||
|
||||
setContextMenuOpen: action,
|
||||
setContextMenuClickLocation: action,
|
||||
setContextMenuPosition: action,
|
||||
setContextMenuMaxHeight: action,
|
||||
setShowProtectedWarning: action,
|
||||
@@ -183,6 +185,10 @@ export class NotesState {
|
||||
this.contextMenuOpen = open;
|
||||
}
|
||||
|
||||
setContextMenuClickLocation(location: { x: number, y: number }): void {
|
||||
this.contextMenuClickLocation = location;
|
||||
}
|
||||
|
||||
setContextMenuPosition(position: {
|
||||
top?: number;
|
||||
left: number;
|
||||
@@ -195,6 +201,60 @@ export class NotesState {
|
||||
this.contextMenuMaxHeight = maxHeight;
|
||||
}
|
||||
|
||||
reloadContextMenuLayout(): void {
|
||||
const { clientHeight } = document.documentElement;
|
||||
const defaultFontSize = window.getComputedStyle(
|
||||
document.documentElement
|
||||
).fontSize;
|
||||
const maxContextMenuHeight = parseFloat(defaultFontSize) * 30;
|
||||
const footerHeight = 32;
|
||||
|
||||
// Open up-bottom is default behavior
|
||||
let openUpBottom = true;
|
||||
|
||||
const bottomSpace = clientHeight - footerHeight - this.contextMenuClickLocation.y;
|
||||
const upSpace = this.contextMenuClickLocation.y;
|
||||
|
||||
// If not enough space to open up-bottom
|
||||
if (maxContextMenuHeight > bottomSpace) {
|
||||
// If there's enough space, open bottom-up
|
||||
if (upSpace > maxContextMenuHeight) {
|
||||
openUpBottom = false;
|
||||
this.setContextMenuMaxHeight(
|
||||
'auto'
|
||||
);
|
||||
// Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space
|
||||
} else {
|
||||
if (upSpace > bottomSpace) {
|
||||
this.setContextMenuMaxHeight(
|
||||
upSpace - 2
|
||||
);
|
||||
openUpBottom = false;
|
||||
} else {
|
||||
this.setContextMenuMaxHeight(
|
||||
bottomSpace - 2
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.setContextMenuMaxHeight(
|
||||
'auto'
|
||||
);
|
||||
}
|
||||
|
||||
if (openUpBottom) {
|
||||
this.setContextMenuPosition({
|
||||
top: this.contextMenuClickLocation.y,
|
||||
left: this.contextMenuClickLocation.x,
|
||||
});
|
||||
} else {
|
||||
this.setContextMenuPosition({
|
||||
bottom: clientHeight - this.contextMenuClickLocation.y,
|
||||
left: this.contextMenuClickLocation.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async changeSelectedNotes(
|
||||
mutate: (mutator: NoteMutator) => void
|
||||
): Promise<void> {
|
||||
|
||||
@@ -170,7 +170,7 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
|
||||
this.$location.search().demo === 'true' &&
|
||||
!this.application.hasAccount()
|
||||
) {
|
||||
await this.application.setHost(
|
||||
await this.application.setCustomHost(
|
||||
'https://syncing-server-demo.standardnotes.org'
|
||||
);
|
||||
this.application.signIn(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
)
|
||||
.sk-app-bar-item
|
||||
a.no-decoration.sk-label.title(
|
||||
href='https://standardnotes.org/help',
|
||||
href='https://standardnotes.com/help',
|
||||
rel='noopener',
|
||||
target='_blank'
|
||||
)
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
ng-class="{'selected' : self.isNoteSelected(note.uuid) }"
|
||||
ng-click='self.selectNote(note, true)'
|
||||
)
|
||||
.note-flags(ng-show='self.noteFlags[note.uuid].length > 0')
|
||||
.note-flags.flex.flex-wrap(ng-show='self.noteFlags[note.uuid].length > 0')
|
||||
.flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]')
|
||||
.label {{flag.text}}
|
||||
.name(ng-show='note.title')
|
||||
|
||||
@@ -310,58 +310,11 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
|
||||
await this.selectNote(note, true);
|
||||
}
|
||||
if (this.state.selectedNotes[note.uuid]) {
|
||||
const { clientHeight } = document.documentElement;
|
||||
const defaultFontSize = window.getComputedStyle(
|
||||
document.documentElement
|
||||
).fontSize;
|
||||
const maxContextMenuHeight = parseFloat(defaultFontSize) * 30;
|
||||
const footerHeight = 32;
|
||||
|
||||
// Open up-bottom is default behavior
|
||||
let openUpBottom = true;
|
||||
|
||||
const bottomSpace = clientHeight - footerHeight - e.clientY;
|
||||
const upSpace = e.clientY;
|
||||
|
||||
// If not enough space to open up-bottom
|
||||
if (maxContextMenuHeight > bottomSpace) {
|
||||
// If there's enough space, open bottom-up
|
||||
if (upSpace > maxContextMenuHeight) {
|
||||
openUpBottom = false;
|
||||
this.appState.notes.setContextMenuMaxHeight(
|
||||
'auto'
|
||||
);
|
||||
// Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space
|
||||
} else {
|
||||
if (upSpace > bottomSpace) {
|
||||
this.appState.notes.setContextMenuMaxHeight(
|
||||
upSpace - 2
|
||||
);
|
||||
openUpBottom = false;
|
||||
} else {
|
||||
this.appState.notes.setContextMenuMaxHeight(
|
||||
bottomSpace - 2
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.appState.notes.setContextMenuMaxHeight(
|
||||
'auto'
|
||||
);
|
||||
}
|
||||
|
||||
if (openUpBottom) {
|
||||
this.appState.notes.setContextMenuPosition({
|
||||
top: e.clientY,
|
||||
left: e.clientX,
|
||||
});
|
||||
} else {
|
||||
this.appState.notes.setContextMenuPosition({
|
||||
bottom: clientHeight - e.clientY,
|
||||
left: e.clientX,
|
||||
});
|
||||
}
|
||||
|
||||
this.appState.notes.setContextMenuClickLocation({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
});
|
||||
this.appState.notes.reloadContextMenuLayout();
|
||||
this.appState.notes.setContextMenuOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-top: -4px;
|
||||
|
||||
.flag {
|
||||
padding: 4px;
|
||||
@@ -176,6 +177,7 @@
|
||||
padding-right: 6px;
|
||||
border-radius: var(--sn-stylekit-general-border-radius);
|
||||
margin-right: 4px;
|
||||
margin-top: 4px;
|
||||
|
||||
&.info {
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.sn-component
|
||||
.sk-menu-panel.dropdown-menu
|
||||
a.no-decoration(
|
||||
href='https://standardnotes.org/extensions',
|
||||
href='https://standardnotes.com/extensions',
|
||||
ng-if='self.state.extensions.length == 0',
|
||||
rel='noopener',
|
||||
target='blank'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
.sk-app-bar-item-column
|
||||
div
|
||||
a.sk-label.sk-base(
|
||||
href='https://dashboard.standardnotes.org',
|
||||
href='https://dashboard.standardnotes.com',
|
||||
rel='noopener',
|
||||
target='_blank'
|
||||
)
|
||||
@@ -29,7 +29,7 @@
|
||||
.sk-app-bar-item
|
||||
.sk-app-bar-item-column
|
||||
a.sn-button.small.warning(
|
||||
href='https://standardnotes.org/help/41/expired',
|
||||
href='https://standardnotes.com/help/41/expired',
|
||||
rel='noopener',
|
||||
target='_blank'
|
||||
) Help
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
ng-if='editor.conflictOf'
|
||||
) Conflicted copy
|
||||
a.no-decoration(
|
||||
href='https://standardnotes.org/extensions',
|
||||
href='https://standardnotes.com/extensions',
|
||||
ng-if='self.state.editors.length == 0',
|
||||
rel='noopener',
|
||||
target='blank'
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
| Extensions use an offline messaging system to communicate. Learn more at
|
||||
|
|
||||
a.sk-a.info(
|
||||
href='https://standardnotes.org/permissions',
|
||||
href='https://standardnotes.com/permissions',
|
||||
rel='noopener',
|
||||
target='_blank'
|
||||
) https://standardnotes.org/permissions.
|
||||
) https://standardnotes.com/permissions.
|
||||
.sk-panel-footer
|
||||
button.sn-button.info.block.w-full.text-base.py-3(
|
||||
ng-click='ctrl.accept()'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.8.9",
|
||||
"version": "3.8.13",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -71,7 +71,7 @@
|
||||
"@reach/checkbox": "^0.13.2",
|
||||
"@reach/dialog": "^0.13.0",
|
||||
"@standardnotes/sncrypto-web": "1.2.10",
|
||||
"@standardnotes/snjs": "2.7.6",
|
||||
"@standardnotes/snjs": "2.7.9",
|
||||
"mobx": "^6.1.6",
|
||||
"mobx-react-lite": "^3.2.0",
|
||||
"preact": "^10.5.12"
|
||||
|
||||
@@ -1936,10 +1936,10 @@
|
||||
"@standardnotes/sncrypto-common" "^1.2.7"
|
||||
libsodium-wrappers "^0.7.8"
|
||||
|
||||
"@standardnotes/snjs@2.7.6":
|
||||
version "2.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.7.6.tgz#f0b965bfad61e93af6803ec2698c02b1489d1073"
|
||||
integrity sha512-E1Gj02gWvqypVpPed2YCSUnMUi2ZqFsb8NT2Jo8dqFS6Wk3FMzptWlFM/DuTifyzaYsmwPkXv1v5KpeU+dA+CQ==
|
||||
"@standardnotes/snjs@2.7.9":
|
||||
version "2.7.9"
|
||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.7.9.tgz#8b2a5d0f6248e1872b2fe68a73307f754528b4c3"
|
||||
integrity sha512-FNiLXdaUb2+WpCteTbfg/iiv4k0UXFzz57RnY1O8X40vEVFVY8VjrJuJxxwDuZqidUZeLIdTMlmfdKWoD+NRDA==
|
||||
dependencies:
|
||||
"@standardnotes/auth" "^2.0.0"
|
||||
"@standardnotes/sncrypto-common" "^1.2.9"
|
||||
|
||||
Reference in New Issue
Block a user