Merge branch 'release/3.6.2' into main

This commit is contained in:
Baptiste Grob
2021-03-11 11:21:39 +01:00
15 changed files with 207 additions and 151 deletions

View File

@@ -40,14 +40,14 @@ function useSessions(
(async () => {
setRefreshing(true);
const response = await application.getSessions();
if ('error' in response) {
if ('error' in response || !response.data) {
if (response.error?.message) {
setErrorMessage(response.error.message);
} else {
setErrorMessage('An unknown error occured while loading sessions.');
}
} else {
const sessions = response as Session[];
const sessions = response.data;
setSessions(sessions);
setErrorMessage('');
}

View File

@@ -7,21 +7,25 @@ import {
PayloadContent,
} from '@standardnotes/snjs';
function zippableTxtName(name: string, suffix = ""): string {
const sanitizedName = name
.replace(/\//g, '')
.replace(/\\+/g, '')
.replace(/:/g, ' ')
.replace(/\./g, ' ');
const nameEnd = suffix + ".txt";
function sanitizeFileName(name: string) {
return name
.replace(/\//g, '')
.replace(/\\+/g, '')
.replace(/:/g, ' ')
.replace(/\|/g, ' ')
.replace(/\./g, ' ');
}
function zippableTxtName(name: string, suffix = ''): string {
const sanitizedName = sanitizeFileName(name);
const nameEnd = suffix + '.txt';
const maxFileNameLength = 255;
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd;
}
export class ArchiveManager {
private readonly application: WebApplication
private textFile?: string
private readonly application: WebApplication;
private textFile?: string;
constructor(application: WebApplication) {
this.application = application;
@@ -36,10 +40,9 @@ export class ArchiveManager {
if (!data) {
return;
}
const blobData = new Blob(
[JSON.stringify(data, null, 2)],
{ type: 'text/json' }
);
const blobData = new Blob([JSON.stringify(data, null, 2)], {
type: 'text/json',
});
if (encrypted) {
this.downloadData(
blobData,
@@ -85,21 +88,18 @@ export class ArchiveManager {
});
}
private async downloadZippedDecryptedItems(
data: BackupFile
) {
private async downloadZippedDecryptedItems(data: BackupFile) {
await this.loadZip();
const items = data.items;
this.zip.createWriter(
new this.zip.BlobWriter('application/zip'),
async (zipWriter: any) => {
await new Promise((resolve) => {
const blob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: 'text/plain' }
);
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'text/plain',
});
const fileName = zippableTxtName(
'Standard Notes Backup and Import File.txt'
'Standard Notes Backup and Import File'
);
zipWriter.add(fileName, new this.zip.BlobReader(blob), resolve);
});
@@ -120,7 +120,8 @@ export class ArchiveManager {
name = '';
}
const blob = new Blob([contents], { type: 'text/plain' });
const fileName = `Items/${item.content_type}/` +
const fileName =
`Items/${sanitizeFileName(item.content_type)}/` +
zippableTxtName(name, `-${item.uuid.split('-')[0]}`);
zipWriter.add(fileName, new this.zip.BlobReader(blob), () => {
index++;
@@ -138,7 +139,9 @@ export class ArchiveManager {
});
};
nextFile();
}, onerror);
},
onerror
);
}
private hrefForData(data: Blob) {

View File

@@ -168,6 +168,7 @@ export class AppState {
readonly noAccountWarning: NoAccountWarningState;
readonly sync = new SyncState();
isSessionsModalVisible = false;
mouseUp = Promise.resolve();
private appEventObserverRemovers: (() => void)[] = [];
@@ -195,6 +196,7 @@ export class AppState {
this.notifyEvent(event);
};
this.registerVisibilityObservers();
document.addEventListener('mousedown', this.onMouseDown);
if (this.bridge.appVersion.includes('-beta')) {
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true;
@@ -231,9 +233,16 @@ export class AppState {
this.rootScopeCleanup2 = undefined;
}
document.removeEventListener('visibilitychange', this.onVisibilityChange);
document.removeEventListener('mousedown', this.onMouseDown);
this.onVisibilityChange = undefined;
}
onMouseDown = (): void => {
this.mouseUp = new Promise((resolve) => {
document.addEventListener('mouseup', () => resolve(), { once: true });
});
};
openSessionsModal() {
this.isSessionsModalVisible = true;
}
@@ -259,7 +268,7 @@ export class AppState {
async createEditor(title?: string) {
const activeEditor = this.getActiveEditor();
const activeTagUuid = this.selectedTag
? this.selectedTag.isSmartTag()
? this.selectedTag.isSmartTag
? undefined
: this.selectedTag.uuid
: undefined;

View File

@@ -63,8 +63,7 @@ export class WebApplication extends SNApplication {
WebCrypto,
new AlertService(),
identifier,
undefined,
undefined,
[],
defaultSyncServerHost
);
this.$compile = $compile;

View File

@@ -460,7 +460,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
await this.editor.insertTemplatedNote();
}
const selectedTag = this.appState.selectedTag;
if (!selectedTag?.isSmartTag() && !selectedTag?.hasRelationshipWithItem(note)) {
if (!selectedTag?.isSmartTag && !selectedTag?.hasRelationshipWithItem(note)) {
await this.application.changeItem(
selectedTag!.uuid,
(mutator) => {
@@ -1052,7 +1052,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
if (savedUuid === this.note.uuid) {
const selectedTag = this.appState.selectedTag;
if (
!selectedTag?.isSmartTag() &&
!selectedTag?.isSmartTag &&
!selectedTag?.hasRelationshipWithItem(this.note)
) {
this.application.changeAndSaveItem(

View File

@@ -1,69 +0,0 @@
import { SNNote } from '@standardnotes/snjs';
export function notePassesFilter(
note: SNNote,
showArchived: boolean,
hidePinned: boolean,
filterText: string
): boolean {
const canShowArchived = showArchived;
const canShowPinned = !hidePinned;
if ((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) {
return false;
}
if (note.protected) {
const match = noteMatchesQuery(note, filterText);
/** Only match title to prevent leaking protected note text */
return match === Match.Title || match === Match.TitleAndText;
} else {
return noteMatchesQuery(note, filterText) !== Match.None;
}
}
enum Match {
None = 0,
Title = 1,
Text = 2,
TitleAndText = Title + Text,
Uuid = 5,
}
function noteMatchesQuery(note: SNNote, query: string): Match {
if (query.length === 0) {
return Match.TitleAndText;
}
const title = note.safeTitle().toLowerCase();
const text = note.safeText().toLowerCase();
const lowercaseText = query.toLowerCase();
const words = lowercaseText.split(' ');
const quotedText = stringBetweenQuotes(lowercaseText);
if (quotedText) {
return (
(title.includes(quotedText) ? Match.Title : Match.None) +
(text.includes(quotedText) ? Match.Text : Match.None)
);
}
if (stringIsUuid(lowercaseText)) {
return note.uuid === lowercaseText ? Match.Uuid : Match.None;
}
const matchesTitle = words.every((word) => {
return title.indexOf(word) >= 0;
});
const matchesBody = words.every((word) => {
return text.indexOf(word) >= 0;
});
return (matchesTitle ? Match.Title : 0) + (matchesBody ? Match.Text : 0);
}
function stringBetweenQuotes(text: string) {
const matches = text.match(/"(.*?)"/);
return matches ? matches[1] : null;
}
function stringIsUuid(text: string) {
const matches = text.match(
/\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/
);
// eslint-disable-next-line no-unneeded-ternary
return matches ? true : false;
}

View File

@@ -1,7 +1,7 @@
#notes-column.sn-component.section.notes(aria-label='Notes')
.content
#notes-title-bar.section-title-bar
.padded
.p-4.pt-0
.section-title-bar-header
.sk-h2.font-semibold.title {{self.state.panelTitle}}
.sk-button.contrast.wide(
@@ -12,18 +12,34 @@
i.icon.ion-plus.add-button
.filter-section(role='search')
input#search-bar.filter-bar(
ng-blur='self.onFilterEnter()',
ng-ref='self.searchBarInput'
ng-focus='self.onSearchInputFocus()'
ng-blur='self.onSearchInputBlur()',
ng-change='self.filterTextChanged()',
ng-keyup='$event.keyCode == 13 && self.onFilterEnter();',
ng-model='self.state.noteFilter.text',
placeholder='Search',
select-on-focus='true',
title='Searches notes in the currently selected tag'
)
)
#search-clear-button(
ng-click='self.clearFilterText();',
ng-show='self.state.noteFilter.text'
) ✕
label.sk-panel-row.justify-left.mt-2.animate-slide-in-top(
ng-if='self.state.searchIsFocused || self.state.searchOptionsAreFocused || self.state.authorizingSearchOptions'
style="padding-bottom: 0"
)
.sk-horizontal-group.tight
input(
ng-ref='self.searchOptionsInput'
ng-focus="self.onSearchOptionsFocus()"
ng-blur="self.onSearchOptionsBlur()"
type="checkbox"
ng-checked="self.state.noteFilter.includeProtectedNoteText"
ng-on-click="self.onIncludeProtectedNoteTextChange($event)"
)
p.sk-p.capitalize Include protected contents
no-account-warning(
application='self.application'
app-state='self.appState'

View File

@@ -9,6 +9,8 @@ import {
PrefKey,
findInArray,
CollectionSort,
UuidString,
NotesDisplayCriteria
} from '@standardnotes/snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent } from '@/ui_models/app_state';
@@ -16,10 +18,6 @@ import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import {
PANEL_NAME_NOTES
} from '@/views/constants';
import {
notePassesFilter
} from './note_utils';
import { UuidString } from '@standardnotes/snjs';
type NotesState = {
panelTitle: string
@@ -33,7 +31,13 @@ type NotesState = {
hideNotePreview?: boolean
hideDate?: boolean
hideTags: boolean
noteFilter: { text: string }
noteFilter: {
text: string;
includeProtectedNoteText: boolean;
}
searchIsFocused: boolean;
searchOptionsAreFocused: boolean;
authorizingSearchOptions: boolean;
mutable: { showMenu: boolean }
completedFullSync: boolean
[PrefKey.TagsPanelWidth]?: number
@@ -72,6 +76,8 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
private searchKeyObserver: any
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
private removeObservers: Array<() => void> = [];
private searchBarInput?: JQLite;
private searchOptionsInput?: JQLite;
/* @ngInject */
constructor($timeout: ng.ITimeoutService,) {
@@ -127,10 +133,16 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
renderedNotes: [],
renderedNotesTags: [],
mutable: { showMenu: false },
noteFilter: { text: '' },
noteFilter: {
text: '',
includeProtectedNoteText: false
},
panelTitle: '',
completedFullSync: false,
hideTags: true,
searchIsFocused: false,
searchOptionsAreFocused: false,
authorizingSearchOptions: false
};
}
@@ -211,7 +223,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
*/
private async createPlaceholderNote() {
const selectedTag = this.selectedTag!;
if (selectedTag.isSmartTag() && !selectedTag.isAllTag) {
if (selectedTag.isSmartTag && !selectedTag.isAllTag) {
return;
}
return this.createNewNote();
@@ -330,20 +342,20 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
* an index is roughly O(n^2).
*/
private reloadNotesDisplayOptions() {
const tag = this.appState.selectedTag!;
this.application!.setNotesDisplayOptions(
tag,
this.state.sortBy! as CollectionSort,
this.state.sortReverse! ? 'asc' : 'dsc',
(note: SNNote) => {
return notePassesFilter(
note,
this.getState().showArchived! || tag?.isArchiveTag || tag?.isTrashTag,
this.getState().hidePinned!,
this.getState().noteFilter.text.toLowerCase()
);
const tag = this.appState.selectedTag;
const searchText = this.getState().noteFilter.text.toLowerCase();
const criteria = NotesDisplayCriteria.Create({
sortProperty: this.state.sortBy! as CollectionSort,
sortDirection: this.state.sortReverse! ? 'asc' : 'dsc',
tags: tag ? [tag] : [],
includeArchived: this.getState().showArchived!,
includePinned: !this.getState().hidePinned!,
searchQuery: {
query: searchText ?? '',
includeProtectedNoteText: this.state.noteFilter.includeProtectedNoteText
}
);
});
this.application!.setNotesDisplayCriteria(criteria);
}
private get selectedTag() {
@@ -376,7 +388,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
const selectedTag = this.appState.selectedTag;
if (!selectedTag) {
return [];
} else if (selectedTag?.isSmartTag()) {
} else if (selectedTag?.isSmartTag) {
return notes.map((note) =>
this.appState
.getNoteTags(note)
@@ -691,6 +703,64 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
await this.reloadNotes();
}
async onIncludeProtectedNoteTextChange(event: Event) {
this.searchBarInput?.[0].focus();
if (this.state.noteFilter.includeProtectedNoteText) {
this.state.noteFilter.includeProtectedNoteText = false;
this.reloadNotesDisplayOptions();
await this.reloadNotes();
} else {
this.setState({
authorizingSearchOptions: true,
});
event.preventDefault();
if (await this.application.authorizeSearchingProtectedNotesText()) {
this.state.noteFilter.includeProtectedNoteText = true;
this.reloadNotesDisplayOptions();
await this.reloadNotes();
}
await this.$timeout(50);
await this.setState({
authorizingSearchOptions: false,
});
}
}
onSearchInputFocus() {
this.setState({
searchIsFocused: true,
});
}
async onSearchInputBlur() {
await this.appState.mouseUp;
/**
* Wait a non-zero amount of time so the newly-focused element can have
* enough time to set its state
*/
await this.$timeout(50);
await this.setState({
searchIsFocused:
this.searchBarInput?.[0] === document.activeElement,
});
this.onFilterEnter();
}
onSearchOptionsFocus() {
this.setState({
searchOptionsAreFocused: true,
});
}
async onSearchOptionsBlur() {
await this.appState.mouseUp;
await this.$timeout(50);
this.setState({
searchOptionsAreFocused:
this.searchOptionsInput?.[0] === document.activeElement,
});
}
onFilterEnter() {
/**
* For Desktop, performing a search right away causes

View File

@@ -180,7 +180,7 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
if (tag === this.state.templateTag) {
continue;
}
if (tag.isSmartTag()) {
if (tag.isSmartTag) {
/** Other smart tags do not contain counts */
if (tag.isAllTag) {
const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag)
@@ -246,12 +246,8 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
this.selectTag(tag as SNTag);
}
} else if (data.item!.content_type === ContentType.SmartTag) {
this.application.createTemplateItem(
ContentType.SmartTag,
data.item!.content as PayloadContent
).then(smartTag => {
this.selectTag(smartTag as SNSmartTag);
});
const matchingTag = this.getState().smartTags.find(t => t.uuid === data.item!.uuid);
this.selectTag(matchingTag as SNSmartTag);
}
} else if (action === ComponentAction.ClearSelection) {
this.selectTag(this.getState().smartTags[0]);

View File

@@ -206,10 +206,6 @@ $footer-height: 32px;
.section-title-bar {
.padded {
padding: 0 14px;
}
.add-button {
font-size: 12px;
}

View File

@@ -42,7 +42,6 @@
#notes-menu-bar {
position: relative;
margin-top: 14px;
}
#notes-options-menu {

View File

@@ -97,13 +97,37 @@ $screen-md-max: ($screen-lg-min - 1) !default;
justify-self: flex-start;
}
.animate-slide-in-top {
animation: slide-in-top .1s ease-out;
}
@keyframes slide-in-top {
0% {
opacity: 0;
transform: translateY(-40%);
}
75% {
opacity: 1;
}
100% {
transform: translateY(0%);
}
}
.m-0 {
margin: 0;
}
.mb-0 {
margin-bottom: 0;
}
.mt-1 {
margin-top: .25rem;
}
.mt-2 {
margin-top: .5rem;
}
.mt-3 {
margin-top: .75rem;
}
@@ -114,10 +138,17 @@ $screen-md-max: ($screen-lg-min - 1) !default;
.p-0 {
padding: 0rem;
}
.p-4 {
padding: 1rem;
}
.p-5 {
padding: 1.25rem;
}
.pt-0 {
padding-top: 0;
}
.px-3 {
padding-left: .75rem;
padding-right: .75rem;

View File

@@ -149,7 +149,7 @@
.sk-panel-row
.sk-panel-column
.sk-h1.sk-bold.wrap {{self.state.user.email}}
.sk-subtitle.subtle.normal {{self.state.server}}
.sk-subtitle.neutral {{self.state.server}}
.sk-panel-row
a.sk-a.info.sk-panel-row.condensed(
ng-click="self.openPasswordWizard()"
@@ -303,11 +303,11 @@
.sk-panel-footer
.sk-panel-row
.sk-p.left.neutral
span.faded {{self.state.appVersion}}
span {{self.state.appVersion}}
span(ng-if="self.state.showBetaWarning")
span.faded (
span (
a.sk-a(ng-click="self.appState.disableBetaWarning()") Hide beta warning
span.faded )
span )
a.sk-a.right(
ng-click='self.hidePasswordForm()',
ng-if='self.state.formData.showLogin || self.state.formData.showRegister'

View File

@@ -1,6 +1,6 @@
{
"name": "standard-notes-web",
"version": "3.6.1",
"version": "3.6.2",
"license": "AGPL-3.0-or-later",
"repository": {
"type": "git",
@@ -53,7 +53,7 @@
"pug-loader": "^2.4.0",
"sass-loader": "^8.0.2",
"serve-static": "^1.14.1",
"sn-stylekit": "^2.2.0",
"sn-stylekit": "^2.2.1",
"ts-loader": "^8.0.17",
"typescript": "^4.1.5",
"typescript-eslint": "0.0.1-alpha.0",
@@ -68,7 +68,7 @@
"@reach/alert-dialog": "^0.13.0",
"@reach/dialog": "^0.13.0",
"@standardnotes/sncrypto-web": "^1.2.10",
"@standardnotes/snjs": "^2.0.61",
"@standardnotes/snjs": "^2.0.67",
"mobx": "^6.1.6",
"preact": "^10.5.12"
}

View File

@@ -1832,6 +1832,11 @@
dependencies:
tslib "^2.0.0"
"@standardnotes/auth@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-2.0.0.tgz#93f633fd40855f87843f911109e92b29dcbc5a04"
integrity sha512-B2NznCm3pzwBvBU/YQfuDrtlEbLO3hNH3QrqSwK2dFwUGAnl5UQPC9FKFWYgly05rWevwMY3IUmiZRzUEVlKsQ==
"@standardnotes/sncrypto-common@^1.2.7", "@standardnotes/sncrypto-common@^1.2.9":
version "1.2.9"
resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.2.9.tgz#5212a959e4ec563584e42480bfd39ef129c3cbdf"
@@ -1845,11 +1850,12 @@
"@standardnotes/sncrypto-common" "^1.2.7"
libsodium-wrappers "^0.7.8"
"@standardnotes/snjs@^2.0.61":
version "2.0.61"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.61.tgz#82608ef48830a80afbc9468ed2d308e59e4e9a08"
integrity sha512-MVnzT7cX7oIak9g/xlOJKfWCfBygw8kXtuVHSofdxPhYIPIr1MwK4xkkqho/ZShVfPQEESyj3RaEsOIzKuuL4w==
"@standardnotes/snjs@^2.0.67":
version "2.0.67"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.67.tgz#87e29f40bb5efaa36f30ddc5905164f7dce194d9"
integrity sha512-XCDxlFQCh0zmV3Hc9mjU7ritZ/2Ma5JPoCbDy4CIAlkKdmVL4tu/4jCfRFILM0zpKF/kLsCTbLGdG7TgU/ReKg==
dependencies:
"@standardnotes/auth" "^2.0.0"
"@standardnotes/sncrypto-common" "^1.2.9"
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
@@ -7802,10 +7808,10 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
sn-stylekit@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-2.2.0.tgz#0c143cb25addf4a1edf02d41ea1d6821017f8652"
integrity sha512-xUx+EujROZBaoMrvM0y7CjHPD6WJfwu1tY3oeFG/sV3M9YvCcGkX76Jz9iPt0daeYM4h0oAWphniH7pLi3aFwA==
sn-stylekit@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-2.2.1.tgz#4a81a05b2b2d67a11af9d06f3964ac1ece3aa84a"
integrity sha512-mrvUbf2HbWOfxbNglo7RXa5JBe9UV9rurupeGoX/Kh4/lVlB2n/56ZT103xk/4tp0/mVCpxjD7Dt1stKFxsvFA==
snapdragon-node@^2.0.1:
version "2.1.1"