Notes TypeScript

This commit is contained in:
Mo Bitar
2020-04-11 23:26:02 -05:00
parent 2bc3658f1a
commit 3542cba002
7 changed files with 301 additions and 245 deletions

View File

@@ -1,3 +1,4 @@
import { AppStateEvent } from '@/services/state';
import { WebApplication } from './../../application'; import { WebApplication } from './../../application';
import { ApplicationEvent } from 'snjs'; import { ApplicationEvent } from 'snjs';
@@ -82,10 +83,11 @@ export class PureCtrl {
} }
addAppStateObserver() { addAppStateObserver() {
this.unsubState = this.application!.getAppState() this.unsubState = this.application!.getAppState().addObserver(
.addObserver((eventName: any, data: any) => { async (eventName, data) => {
this.onAppStateEvent(eventName, data); this.onAppStateEvent(eventName, data);
}); }
);
} }
onAppStateEvent(eventName: any, data: any) { onAppStateEvent(eventName: any, data: any) {

View File

@@ -1,51 +1,55 @@
export const SORT_KEY_CREATED_AT = 'created_at'; import { SNNote, SNTag } from 'snjs';
export const SORT_KEY_UPDATED_AT = 'updated_at';
export const SORT_KEY_CLIENT_UPDATED_AT = 'client_updated_at';
export const SORT_KEY_TITLE = 'title';
export function filterAndSortNotes({ export enum NoteSortKey {
notes, CreatedAt = 'created_at',
selectedTag, UpdatedAt = 'updated_at',
showArchived, ClientUpdatedAt = 'client_updated_at',
hidePinned, Title = 'title',
filterText, }
sortBy,
reverse export function filterAndSortNotes(
}) { notes: SNNote[],
const filtered = filterNotes({ selectedTag: SNTag,
showArchived: boolean,
hidePinned: boolean,
filterText: string,
sortBy: string,
reverse: boolean,
) {
const filtered = filterNotes(
notes, notes,
selectedTag, selectedTag,
showArchived, showArchived,
hidePinned, hidePinned,
filterText, filterText,
}); );
const sorted = sortNotes({ const sorted = sortNotes(
notes: filtered, filtered,
sortBy, sortBy,
reverse reverse
}); );
return sorted; return sorted;
} }
export function filterNotes({ export function filterNotes(
notes, notes: SNNote[],
selectedTag, selectedTag: SNTag,
showArchived, showArchived: boolean,
hidePinned, hidePinned: boolean,
filterText filterText: string
}) { ) {
return notes.filter((note) => { return notes.filter((note) => {
let canShowArchived = showArchived; let canShowArchived = showArchived;
const canShowPinned = !hidePinned; const canShowPinned = !hidePinned;
const isTrash = selectedTag.content.isTrashTag; const isTrash = selectedTag.isTrashTag;
if (!isTrash && note.content.trashed) { if (!isTrash && note.trashed) {
return false; return false;
} }
const isSmartTag = selectedTag.isSmartTag(); const isSmartTag = selectedTag.isSmartTag();
if (isSmartTag) { if (isSmartTag) {
canShowArchived = ( canShowArchived = (
canShowArchived || canShowArchived ||
selectedTag.content.isArchiveTag || selectedTag.isArchiveTag ||
isTrash isTrash
); );
} }
@@ -55,17 +59,14 @@ export function filterNotes({
) { ) {
return false; return false;
} }
return noteMatchesQuery({ return noteMatchesQuery(note, filterText);
note,
query: filterText
});
}); });
} }
function noteMatchesQuery({ function noteMatchesQuery(
note, note: SNNote,
query query: string
}) { ) {
if(query.length === 0) { if(query.length === 0) {
return true; return true;
} }
@@ -93,12 +94,12 @@ function noteMatchesQuery({
return matchesTitle || matchesBody; return matchesTitle || matchesBody;
} }
function stringBetweenQuotes(text) { function stringBetweenQuotes(text: string) {
const matches = text.match(/"(.*?)"/); const matches = text.match(/"(.*?)"/);
return matches ? matches[1] : null; return matches ? matches[1] : null;
} }
function stringIsUuid(text) { function stringIsUuid(text: string) {
const matches = text.match( 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/ /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/
); );
@@ -106,12 +107,12 @@ function stringIsUuid(text) {
return matches ? true : false; return matches ? true : false;
} }
export function sortNotes({ export function sortNotes(
notes = [], notes: SNNote[] = [],
sortBy, sortBy: string,
reverse reverse: boolean
}) { ) {
const sortValueFn = (a, b, pinCheck = false) => { const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => {
if (a.dummy) { return -1; } if (a.dummy) { return -1; }
if (b.dummy) { return 1; } if (b.dummy) { return 1; }
if (!pinCheck) { if (!pinCheck) {
@@ -121,14 +122,13 @@ export function sortNotes({
if (a.pinned) { return -1; } if (a.pinned) { return -1; }
if (b.pinned) { return 1; } if (b.pinned) { return 1; }
} }
let aValue = (a as any)[sortBy] || '';
let aValue = a[sortBy] || ''; let bValue = (a as any)[sortBy] || '';
let bValue = b[sortBy] || '';
let vector = 1; let vector = 1;
if (reverse) { if (reverse) {
vector *= -1; vector *= -1;
} }
if (sortBy === SORT_KEY_TITLE) { if (sortBy === NoteSortKey.Title) {
aValue = aValue.toLowerCase(); aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase(); bValue = bValue.toLowerCase();
if (aValue.length === 0 && bValue.length === 0) { if (aValue.length === 0 && bValue.length === 0) {

View File

@@ -1,7 +1,8 @@
import { PanelPuppet, WebDirective } from './../../types';
import angular from 'angular'; import angular from 'angular';
import template from '%/notes.pug'; import template from '%/notes.pug';
import { ApplicationEvent, ContentTypes, removeFromArray } from 'snjs'; import { ApplicationEvent, ContentType, removeFromArray, SNNote, SNTag } from 'snjs';
import { PureCtrl } from '@Controllers'; import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import { import {
@@ -11,12 +12,31 @@ import {
PANEL_NAME_NOTES PANEL_NAME_NOTES
} from '@/controllers/constants'; } from '@/controllers/constants';
import { import {
SORT_KEY_CREATED_AT, NoteSortKey,
SORT_KEY_UPDATED_AT,
SORT_KEY_CLIENT_UPDATED_AT,
SORT_KEY_TITLE,
filterAndSortNotes filterAndSortNotes
} from './note_utils'; } from './note_utils';
import { UuidString } from '@/../../../../snjs/dist/@types/types';
type NotesState = {
tag?: SNTag
notes?: SNNote[]
renderedNotes?: SNNote[]
selectedNote?: SNNote
sortBy?: string
sortReverse?: boolean
showArchived?: boolean
hidePinned?: boolean
hideNotePreview?: boolean
hideDate?: boolean
hideTags?: boolean
noteFilter: { text: string }
mutable: { showMenu: boolean }
}
type NoteFlag = {
text: string
class: 'info' | 'neutral' | 'warning' | 'success' | 'danger'
}
/** /**
* This is the height of a note cell with nothing but the title, * This is the height of a note cell with nothing but the title,
@@ -29,10 +49,19 @@ const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
class NotesCtrl extends PureCtrl { class NotesCtrl extends PureCtrl {
private panelPuppet?: PanelPuppet
private reloadNotesPromise?: any
private notesToDisplay = 0
private pageSize = 0
private searchSubmitted = false
private newNoteKeyObserver: any
private nextNoteKeyObserver: any
private previousNoteKeyObserver: any
private searchKeyObserver: any
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
/* @ngInject */ /* @ngInject */
constructor( constructor($timeout: ng.ITimeoutService, ) {
$timeout,
) {
super($timeout); super($timeout);
this.resetPagination(); this.resetPagination();
} }
@@ -46,53 +75,62 @@ class NotesCtrl extends PureCtrl {
onReady: () => this.reloadPreferences() onReady: () => this.reloadPreferences()
}; };
this.onWindowResize = this.onWindowResize.bind(this); this.onWindowResize = this.onWindowResize.bind(this);
this.onPanelResize = this.onPanelResize.bind(this);
window.addEventListener('resize', this.onWindowResize, true); window.addEventListener('resize', this.onWindowResize, true);
this.registerKeyboardShortcuts(); this.registerKeyboardShortcuts();
} }
onWindowResize() { onWindowResize() {
this.resetPagination({ this.resetPagination(true);
keepCurrentIfLarger: true
});
} }
deinit() { deinit() {
this.panelPuppet.onReady = null; this.panelPuppet!.onReady = undefined;
this.panelPuppet = null; this.panelPuppet = undefined;
window.removeEventListener('resize', this.onWindowResize, true); window.removeEventListener('resize', this.onWindowResize, true);
this.onWindowResize = null; (this.onWindowResize as any) = undefined;
this.onPanelResize = null; (this.onPanelResize as any) = undefined;
this.newNoteKeyObserver();
this.nextNoteKeyObserver();
this.previousNoteKeyObserver();
this.searchKeyObserver();
this.newNoteKeyObserver = undefined;
this.nextNoteKeyObserver = undefined;
this.previousNoteKeyObserver = undefined;
this.searchKeyObserver = undefined;
super.deinit(); super.deinit();
} }
getState() {
return this.state as NotesState;
}
getInitialState() { getInitialState() {
return { return {
notes: [], notes: [],
renderedNotes: [], renderedNotes: [],
selectedNote: null,
tag: null,
sortBy: null,
showArchived: null,
hidePinned: null,
sortReverse: null,
panelTitle: null,
mutable: { showMenu: false }, mutable: { showMenu: false },
noteFilter: { text: '' }, noteFilter: { text: '' },
}; } as NotesState;
} }
onAppLaunch() { async onAppLaunch() {
super.onAppLaunch(); super.onAppLaunch();
this.streamNotesAndTags(); this.streamNotesAndTags();
this.reloadPreferences(); this.reloadPreferences();
} }
/** @override */ /** @override */
onAppStateEvent(eventName, data) { onAppStateEvent(eventName: AppStateEvent, data?: any) {
if (eventName === AppStateEvent.TagChanged) { if (eventName === AppStateEvent.TagChanged) {
this.handleTagChange(this.application.getAppState().getSelectedTag(), data.previousTag); this.handleTagChange(
this.application!.getAppState().getSelectedTag()!,
data.previousTag
);
} else if (eventName === AppStateEvent.NoteChanged) { } else if (eventName === AppStateEvent.NoteChanged) {
this.handleNoteSelection(this.application.getAppState().getSelectedNote()); this.handleNoteSelection(
this.application!.getAppState().getSelectedNote()!
);
} else if (eventName === AppStateEvent.PreferencesChanged) { } else if (eventName === AppStateEvent.PreferencesChanged) {
this.reloadPreferences(); this.reloadPreferences();
this.reloadNotes(); this.reloadNotes();
@@ -102,12 +140,12 @@ class NotesCtrl extends PureCtrl {
} }
/** @override */ /** @override */
async onAppEvent(eventName) { async onAppEvent(eventName: ApplicationEvent) {
if (eventName === ApplicationEvent.SignedIn) { if (eventName === ApplicationEvent.SignedIn) {
/** Delete dummy note if applicable */ /** Delete dummy note if applicable */
if (this.state.selectedNote && this.state.selectedNote.dummy) { if (this.getState().selectedNote && this.getState().selectedNote!.dummy) {
this.application.deleteItemLocally({ item: this.state.selectedNote }); this.application!.deleteItemLocally(this.getState().selectedNote!);
await this.selectNote(null); await this.selectNote(undefined);
await this.reloadNotes(); await this.reloadNotes();
} }
} else if (eventName === ApplicationEvent.CompletedSync) { } else if (eventName === ApplicationEvent.CompletedSync) {
@@ -125,7 +163,7 @@ class NotesCtrl extends PureCtrl {
* that may be in progress. This is the sync alternative to `async getMostValidNotes` * that may be in progress. This is the sync alternative to `async getMostValidNotes`
*/ */
getPossiblyStaleNotes() { getPossiblyStaleNotes() {
return this.state.notes; return this.getState().notes!;
} }
/** /**
@@ -146,21 +184,21 @@ class NotesCtrl extends PureCtrl {
* @access private * @access private
*/ */
async createPlaceholderNote() { async createPlaceholderNote() {
const selectedTag = this.application.getAppState().getSelectedTag(); const selectedTag = this.application!.getAppState().getSelectedTag()!;
if (selectedTag.isSmartTag() && !selectedTag.content.isAllTag) { if (selectedTag.isSmartTag() && !selectedTag.isAllTag) {
return; return;
} }
return this.createNewNote(); return this.createNewNote();
} }
streamNotesAndTags() { streamNotesAndTags() {
this.application.streamItems({ this.application!.streamItems(
contentType: [ContentType.Note, ContentType.Tag], [ContentType.Note, ContentType.Tag],
stream: async ({ items }) => { async (items) => {
await this.reloadNotes(); await this.reloadNotes();
const selectedNote = this.state.selectedNote; const selectedNote = this.getState().selectedNote;
if (selectedNote) { if (selectedNote) {
const discarded = selectedNote.deleted || selectedNote.content.trashed; const discarded = selectedNote.deleted || selectedNote.trashed;
if (discarded) { if (discarded) {
this.selectNextOrCreateNew(); this.selectNextOrCreateNew();
} }
@@ -169,87 +207,83 @@ class NotesCtrl extends PureCtrl {
} }
/** Note has changed values, reset its flags */ /** Note has changed values, reset its flags */
const notes = items.filter((item) => item.content_type === ContentType.Note); const notes = items.filter((item) => item.content_type === ContentType.Note) as SNNote[];
for (const note of notes) { for (const note of notes) {
if(note.deleted) { if (note.deleted) {
continue; continue;
} }
this.loadFlagsForNote(note); this.loadFlagsForNote(note);
note.cachedCreatedAtString = note.createdAtString();
note.cachedUpdatedAtString = note.updatedAtString();
} }
} }
}); );
} }
async selectNote(note) { async selectNote(note?: SNNote) {
return this.application.getAppState().setSelectedNote(note); return this.application!.getAppState().setSelectedNote(note);
} }
async createNewNote() { async createNewNote() {
const selectedTag = this.application.getAppState().getSelectedTag(); const selectedTag = this.application!.getAppState().getSelectedTag();
if (!selectedTag) { if (!selectedTag) {
throw 'Attempting to create note with no selected tag'; throw 'Attempting to create note with no selected tag';
} }
let title; let title;
let isDummyNote = true; let isDummyNote = true;
if (this.isFiltering()) { if (this.isFiltering()) {
title = this.state.noteFilter.text; title = this.getState().noteFilter.text;
isDummyNote = false; isDummyNote = false;
} else if (this.state.selectedNote && this.state.selectedNote.dummy) { } else if (this.getState().selectedNote && this.getState().selectedNote!.dummy) {
return; return;
} else { } else {
title = `Note ${this.state.notes.length + 1}`; title = `Note ${this.getState().notes!.length + 1}`;
} }
const newNote = await this.application.createManagedItem({ const newNote = await this.application!.createManagedItem(
contentType: ContentType.Note, ContentType.Note,
content: { {
text: '', text: '',
title: title title: title,
references: []
}, },
override: { true,
dummy: isDummyNote, {
client_updated_at: new Date() dummy: isDummyNote
} }
}); ) as SNNote;
this.application.setItemNeedsSync({ item: newNote });
if (!selectedTag.isSmartTag()) { if (!selectedTag.isSmartTag()) {
selectedTag.addItemAsRelationship(newNote); this.application!.changeItem(selectedTag.uuid, (mutator) => {
this.application.setItemNeedsSync({ item: selectedTag }); mutator.addItemAsRelationship(newNote);
});
} }
this.selectNote(newNote); this.selectNote(newNote);
} }
async handleTagChange(tag, previousTag) { async handleTagChange(tag: SNTag, previousTag?: SNTag) {
if (this.state.selectedNote && this.state.selectedNote.dummy) { if (this.getState().selectedNote && this.getState().selectedNote!.dummy) {
await this.application.deleteItemLocally({ item: this.state.selectedNote }); await this.application!.deleteItemLocally(this.getState().selectedNote!);
if (previousTag) { await this.selectNote(undefined);
removeFromArray(previousTag.notes, this.state.selectedNote);
}
await this.selectNote(null);
} }
await this.setState({ tag: tag }); await this.setState({ tag: tag });
this.resetScrollPosition(); this.resetScrollPosition();
this.setShowMenuFalse(); this.setShowMenuFalse();
await this.setNoteFilterText(''); await this.setNoteFilterText('');
this.application.getDesktopService().searchText(); this.application!.getDesktopService().searchText();
this.resetPagination(); this.resetPagination();
/* Capture db load state before beginning reloadNotes, since this status may change during reload */ /* Capture db load state before beginning reloadNotes, since this status may change during reload */
const dbLoaded = this.application.isDatabaseLoaded(); const dbLoaded = this.application!.isDatabaseLoaded();
await this.reloadNotes(); await this.reloadNotes();
if (this.state.notes.length > 0) { if (this.getState().notes!.length > 0) {
this.selectFirstNote(); this.selectFirstNote();
} else if (dbLoaded) { } else if (dbLoaded) {
if (!tag.isSmartTag() || tag.content.isAllTag) { if (!tag.isSmartTag() || tag.isAllTag) {
this.createPlaceholderNote(); this.createPlaceholderNote();
} else if ( } else if (
this.state.selectedNote && this.getState().selectedNote &&
!this.state.notes.includes(this.state.selectedNote) !this.getState().notes!.includes(this.getState().selectedNote!)
) { ) {
this.selectNote(null); this.selectNote(undefined);
} }
} }
} }
@@ -262,8 +296,8 @@ class NotesCtrl extends PureCtrl {
} }
} }
async removeNoteFromList(note) { async removeNoteFromList(note: SNNote) {
const notes = this.state.notes; const notes = this.getState().notes!;
removeFromArray(notes, note); removeFromArray(notes, note);
await this.setState({ await this.setState({
notes: notes, notes: notes,
@@ -277,23 +311,24 @@ class NotesCtrl extends PureCtrl {
} }
async performPeloadNotes() { async performPeloadNotes() {
if (!this.state.tag) { const tag = this.getState().tag!;
if (!tag) {
return; return;
} }
const notes = filterAndSortNotes({ const tagNotes = this.appState.getTagNotes(tag);
notes: this.state.tag.notes, const notes = filterAndSortNotes(
selectedTag: this.state.tag, tagNotes,
showArchived: this.state.showArchived, tag,
hidePinned: this.state.hidePinned, this.getState().showArchived!,
filterText: this.state.noteFilter.text.toLowerCase(), this.getState().hidePinned!,
sortBy: this.state.sortBy, this.getState().noteFilter.text.toLowerCase(),
reverse: this.state.sortReverse this.getState().sortBy!,
}); this.getState().sortReverse!
);
for (const note of notes) { for (const note of notes) {
if (note.errorDecrypting) { if (note.errorDecrypting) {
this.loadFlagsForNote(note); this.loadFlagsForNote(note);
} }
note.shouldShowTags = this.shouldShowTagsForNote(note);
} }
await this.setState({ await this.setState({
notes: notes, notes: notes,
@@ -305,19 +340,19 @@ class NotesCtrl extends PureCtrl {
setShowMenuFalse() { setShowMenuFalse() {
this.setState({ this.setState({
mutable: { mutable: {
...this.state.mutable, ...this.getState().mutable,
showMenu: false showMenu: false
} }
}); });
} }
async handleNoteSelection(note) { async handleNoteSelection(note: SNNote) {
const previousNote = this.state.selectedNote; const previousNote = this.getState().selectedNote;
if (previousNote === note) { if (previousNote === note) {
return; return;
} }
if (previousNote && previousNote.dummy) { if (previousNote && previousNote.dummy) {
await this.application.deleteItemLocally({ item: previousNote }); await this.application!.deleteItemLocally(previousNote);
this.removeNoteFromList(previousNote); this.removeNoteFromList(previousNote);
} }
await this.setState({ await this.setState({
@@ -326,49 +361,49 @@ class NotesCtrl extends PureCtrl {
if (!note) { if (!note) {
return; return;
} }
this.selectedIndex = Math.max(0, this.displayableNotes().indexOf(note)); if (note.conflictOf) {
if (note.content.conflict_of) { this.application!.changeAndSaveItem(note.uuid, (mutator) => {
note.content.conflict_of = null; mutator.conflictOf = undefined;
this.application.saveItem({ item: note }); })
} }
if (this.isFiltering()) { if (this.isFiltering()) {
this.application.getDesktopService().searchText(this.state.noteFilter.text); this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
} }
} }
reloadPreferences() { reloadPreferences() {
const viewOptions = {}; const viewOptions = {} as NotesState;
const prevSortValue = this.state.sortBy; const prevSortValue = this.getState().sortBy;
let sortBy = this.application.getPrefsService().getValue( let sortBy = this.application!.getPrefsService().getValue(
PrefKeys.SortNotesBy, PrefKeys.SortNotesBy,
SORT_KEY_CREATED_AT NoteSortKey.CreatedAt
); );
if (sortBy === SORT_KEY_UPDATED_AT) { if (sortBy === NoteSortKey.UpdatedAt) {
/** Use client_updated_at instead */ /** Use client_updated_at instead */
sortBy = SORT_KEY_CLIENT_UPDATED_AT; sortBy = NoteSortKey.ClientUpdatedAt;
} }
viewOptions.sortBy = sortBy; viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.application.getPrefsService().getValue( viewOptions.sortReverse = this.application!.getPrefsService().getValue(
PrefKeys.SortNotesReverse, PrefKeys.SortNotesReverse,
false false
); );
viewOptions.showArchived = this.application.getPrefsService().getValue( viewOptions.showArchived = this.application!.getPrefsService().getValue(
PrefKeys.NotesShowArchived, PrefKeys.NotesShowArchived,
false false
); );
viewOptions.hidePinned = this.application.getPrefsService().getValue( viewOptions.hidePinned = this.application!.getPrefsService().getValue(
PrefKeys.NotesHidePinned, PrefKeys.NotesHidePinned,
false false
); );
viewOptions.hideNotePreview = this.application.getPrefsService().getValue( viewOptions.hideNotePreview = this.application!.getPrefsService().getValue(
PrefKeys.NotesHideNotePreview, PrefKeys.NotesHideNotePreview,
false false
); );
viewOptions.hideDate = this.application.getPrefsService().getValue( viewOptions.hideDate = this.application!.getPrefsService().getValue(
PrefKeys.NotesHideDate, PrefKeys.NotesHideDate,
false false
); );
viewOptions.hideTags = this.application.getPrefsService().getValue( viewOptions.hideTags = this.application!.getPrefsService().getValue(
PrefKeys.NotesHideTags, PrefKeys.NotesHideTags,
false false
); );
@@ -378,27 +413,32 @@ class NotesCtrl extends PureCtrl {
if (prevSortValue && prevSortValue !== sortBy) { if (prevSortValue && prevSortValue !== sortBy) {
this.selectFirstNote(); this.selectFirstNote();
} }
const width = this.application.getPrefsService().getValue( const width = this.application!.getPrefsService().getValue(
PrefKeys.NotesPanelWidth PrefKeys.NotesPanelWidth
); );
if (width && this.panelPuppet.ready) { if (width && this.panelPuppet!.ready) {
this.panelPuppet.setWidth(width); this.panelPuppet!.setWidth!(width);
if (this.panelPuppet.isCollapsed()) { if (this.panelPuppet!.isCollapsed!()) {
this.application.getAppState().panelDidResize( this.application!.getAppState().panelDidResize(
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
this.panelPuppet.isCollapsed() this.panelPuppet!.isCollapsed!()
); );
} }
} }
} }
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => { onPanelResize(
this.application.getPrefsService().setUserPrefValue( newWidth: number,
lastLeft: number,
isAtMaxWidth: boolean,
isCollapsed: boolean
) {
this.application!.getPrefsService().setUserPrefValue(
PrefKeys.NotesPanelWidth, PrefKeys.NotesPanelWidth,
newWidth newWidth
); );
this.application.getPrefsService().syncUserPreferences(); this.application!.getPrefsService().syncUserPreferences();
this.application.getAppState().panelDidResize( this.application!.getAppState().panelDidResize(
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
isCollapsed isCollapsed
); );
@@ -408,11 +448,11 @@ class NotesCtrl extends PureCtrl {
this.notesToDisplay += this.pageSize; this.notesToDisplay += this.pageSize;
this.reloadNotes(); this.reloadNotes();
if (this.searchSubmitted) { if (this.searchSubmitted) {
this.application.getDesktopService().searchText(this.state.noteFilter.text); this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
} }
} }
resetPagination({ keepCurrentIfLarger } = {}) { resetPagination(keepCurrentIfLarger = false) {
const clientHeight = document.documentElement.clientHeight; const clientHeight = document.documentElement.clientHeight;
this.pageSize = Math.ceil(clientHeight / MIN_NOTE_CELL_HEIGHT); this.pageSize = Math.ceil(clientHeight / MIN_NOTE_CELL_HEIGHT);
if (this.pageSize === 0) { if (this.pageSize === 0) {
@@ -427,10 +467,10 @@ class NotesCtrl extends PureCtrl {
reloadPanelTitle() { reloadPanelTitle() {
let title; let title;
if (this.isFiltering()) { if (this.isFiltering()) {
const resultCount = this.state.notes.length; const resultCount = this.getState().notes!.length;
title = `${resultCount} search results`; title = `${resultCount} search results`;
} else if (this.state.tag) { } else if (this.getState().tag) {
title = `${this.state.tag.title}`; title = `${this.getState().tag!.title}`;
} }
this.setState({ this.setState({
panelTitle: title panelTitle: title
@@ -439,27 +479,27 @@ class NotesCtrl extends PureCtrl {
optionsSubtitle() { optionsSubtitle() {
let base = ""; let base = "";
if (this.state.sortBy === 'created_at') { if (this.getState().sortBy === 'created_at') {
base += " Date Added"; base += " Date Added";
} else if (this.state.sortBy === 'client_updated_at') { } else if (this.getState().sortBy === 'client_updated_at') {
base += " Date Modified"; base += " Date Modified";
} else if (this.state.sortBy === 'title') { } else if (this.getState().sortBy === 'title') {
base += " Title"; base += " Title";
} }
if (this.state.showArchived) { if (this.getState().showArchived) {
base += " | + Archived"; base += " | + Archived";
} }
if (this.state.hidePinned) { if (this.getState().hidePinned) {
base += " | Pinned"; base += " | Pinned";
} }
if (this.state.sortReverse) { if (this.getState().sortReverse) {
base += " | Reversed"; base += " | Reversed";
} }
return base; return base;
} }
loadFlagsForNote(note) { loadFlagsForNote(note: SNNote) {
const flags = []; const flags = [] as NoteFlag[];
if (note.pinned) { if (note.pinned) {
flags.push({ flags.push({
text: "Pinned", text: "Pinned",
@@ -472,7 +512,7 @@ class NotesCtrl extends PureCtrl {
class: 'warning' class: 'warning'
}); });
} }
if (note.content.protected) { if (note.protected) {
flags.push({ flags.push({
text: "Protected", text: "Protected",
class: 'success' class: 'success'
@@ -484,13 +524,13 @@ class NotesCtrl extends PureCtrl {
class: 'neutral' class: 'neutral'
}); });
} }
if (note.content.trashed) { if (note.trashed) {
flags.push({ flags.push({
text: "Deleted", text: "Deleted",
class: 'danger' class: 'danger'
}); });
} }
if (note.content.conflict_of) { if (note.conflictOf) {
flags.push({ flags.push({
text: "Conflicted Copy", text: "Conflicted Copy",
class: 'danger' class: 'danger'
@@ -515,19 +555,19 @@ class NotesCtrl extends PureCtrl {
class: 'danger' class: 'danger'
}); });
} }
note.flags = flags; this.noteFlags[note.uuid] = flags;
return flags; return flags;
} }
displayableNotes() { displayableNotes() {
return this.state.notes; return this.getState().notes!;
} }
getFirstNonProtectedNote() { getFirstNonProtectedNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.displayableNotes();
let index = 0; let index = 0;
let note = displayableNotes[index]; let note = displayableNotes[index];
while (note && note.content.protected) { while (note && note.protected) {
index++; index++;
if (index >= displayableNotes.length) { if (index >= displayableNotes.length) {
break; break;
@@ -546,7 +586,9 @@ class NotesCtrl extends PureCtrl {
selectNextNote() { selectNextNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.displayableNotes();
const currentIndex = displayableNotes.indexOf(this.state.selectedNote); const currentIndex = displayableNotes.findIndex((candidate) => {
return candidate.uuid === this.getState().selectedNote!.uuid
});
if (currentIndex + 1 < displayableNotes.length) { if (currentIndex + 1 < displayableNotes.length) {
this.selectNote(displayableNotes[currentIndex + 1]); this.selectNote(displayableNotes[currentIndex + 1]);
} }
@@ -556,16 +598,16 @@ class NotesCtrl extends PureCtrl {
const note = this.getFirstNonProtectedNote(); const note = this.getFirstNonProtectedNote();
if (note) { if (note) {
this.selectNote(note); this.selectNote(note);
} else if (!this.state.tag || !this.state.tag.isSmartTag()) { } else if (!this.getState().tag || !this.getState().tag!.isSmartTag()) {
this.createPlaceholderNote(); this.createPlaceholderNote();
} else { } else {
this.selectNote(null); this.selectNote(undefined);
} }
} }
selectPreviousNote() { selectPreviousNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.displayableNotes();
const currentIndex = displayableNotes.indexOf(this.state.selectedNote); const currentIndex = displayableNotes.indexOf(this.getState().selectedNote!);
if (currentIndex - 1 >= 0) { if (currentIndex - 1 >= 0) {
this.selectNote(displayableNotes[currentIndex - 1]); this.selectNote(displayableNotes[currentIndex - 1]);
return true; return true;
@@ -575,14 +617,14 @@ class NotesCtrl extends PureCtrl {
} }
isFiltering() { isFiltering() {
return this.state.noteFilter.text && return this.getState().noteFilter.text &&
this.state.noteFilter.text.length > 0; this.getState().noteFilter.text.length > 0;
} }
async setNoteFilterText(text) { async setNoteFilterText(text: string) {
await this.setState({ await this.setState({
noteFilter: { noteFilter: {
...this.state.noteFilter, ...this.getState().noteFilter,
text: text text: text
} }
}); });
@@ -609,66 +651,49 @@ class NotesCtrl extends PureCtrl {
* enter before highlighting desktop search results. * enter before highlighting desktop search results.
*/ */
this.searchSubmitted = true; this.searchSubmitted = true;
this.application.getDesktopService().searchText(this.state.noteFilter.text); this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
} }
selectedMenuItem() { selectedMenuItem() {
this.setShowMenuFalse(); this.setShowMenuFalse();
} }
togglePrefKey(key) { togglePrefKey(key: string) {
this.application.getPrefsService().setUserPrefValue(key, !this.state[key]); this.application!.getPrefsService().setUserPrefValue(key, !this.state[key]);
this.application.getPrefsService().syncUserPreferences(); this.application!.getPrefsService().syncUserPreferences();
} }
selectedSortByCreated() { selectedSortByCreated() {
this.setSortBy(SORT_KEY_CREATED_AT); this.setSortBy(NoteSortKey.CreatedAt);
} }
selectedSortByUpdated() { selectedSortByUpdated() {
this.setSortBy(SORT_KEY_CLIENT_UPDATED_AT); this.setSortBy(NoteSortKey.ClientUpdatedAt);
} }
selectedSortByTitle() { selectedSortByTitle() {
this.setSortBy(SORT_KEY_TITLE); this.setSortBy(NoteSortKey.Title);
} }
toggleReverseSort() { toggleReverseSort() {
this.selectedMenuItem(); this.selectedMenuItem();
this.application.getPrefsService().setUserPrefValue( this.application!.getPrefsService().setUserPrefValue(
PrefKeys.SortNotesReverse, PrefKeys.SortNotesReverse,
!this.state.sortReverse !this.getState().sortReverse
); );
this.application.getPrefsService().syncUserPreferences(); this.application!.getPrefsService().syncUserPreferences();
} }
setSortBy(type) { setSortBy(type: NoteSortKey) {
this.application.getPrefsService().setUserPrefValue( this.application!.getPrefsService().setUserPrefValue(
PrefKeys.SortNotesBy, PrefKeys.SortNotesBy,
type type
); );
this.application.getPrefsService().syncUserPreferences(); this.application!.getPrefsService().syncUserPreferences();
}
shouldShowTagsForNote(note) {
if (this.state.hideTags || note.content.protected) {
return false;
}
if (this.state.tag.content.isAllTag) {
return note.tags && note.tags.length > 0;
}
if (this.state.tag.isSmartTag()) {
return true;
}
/**
* Inside a tag, only show tags string if
* note contains tags other than this.state.tag
*/
return note.tags && note.tags.length > 1;
} }
getSearchBar() { getSearchBar() {
return document.getElementById(ELEMENT_ID_SEARCH_BAR); return document.getElementById(ELEMENT_ID_SEARCH_BAR)!;
} }
registerKeyboardShortcuts() { registerKeyboardShortcuts() {
@@ -677,7 +702,7 @@ class NotesCtrl extends PureCtrl {
* use Control modifier as well. These rules don't apply to desktop, but * use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent. * probably better to be consistent.
*/ */
this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ this.newNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
key: 'n', key: 'n',
modifiers: [ modifiers: [
KeyboardModifier.Meta, KeyboardModifier.Meta,
@@ -689,7 +714,7 @@ class NotesCtrl extends PureCtrl {
} }
}); });
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ this.nextNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
key: KeyboardKey.Down, key: KeyboardKey.Down,
elements: [ elements: [
document.body, document.body,
@@ -704,7 +729,7 @@ class NotesCtrl extends PureCtrl {
} }
}); });
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ this.previousNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
key: KeyboardKey.Up, key: KeyboardKey.Up,
element: document.body, element: document.body,
onKeyDown: (event) => { onKeyDown: (event) => {
@@ -712,7 +737,7 @@ class NotesCtrl extends PureCtrl {
} }
}); });
this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({ this.searchKeyObserver = this.application!.getKeyboardService().addKeyObserver({
key: "f", key: "f",
modifiers: [ modifiers: [
KeyboardModifier.Meta, KeyboardModifier.Meta,
@@ -726,8 +751,9 @@ class NotesCtrl extends PureCtrl {
} }
} }
export class NotesPanel { export class NotesPanel extends WebDirective {
constructor() { constructor() {
super();
this.template = template; this.template = template;
this.replace = true; this.replace = true;
this.controller = NotesCtrl; this.controller = NotesCtrl;

View File

@@ -20,7 +20,7 @@ enum KeyboardKeyEvent {
}; };
type KeyboardObserver = { type KeyboardObserver = {
key?: KeyboardKey key?: KeyboardKey | string
modifiers?: KeyboardModifier[] modifiers?: KeyboardModifier[]
onKeyDown?: (event: KeyboardEvent) => void onKeyDown?: (event: KeyboardEvent) => void
onKeyUp?: (event: KeyboardEvent) => void onKeyUp?: (event: KeyboardEvent) => void
@@ -87,7 +87,7 @@ export class KeyboardManager {
eventMatchesKeyAndModifiers( eventMatchesKeyAndModifiers(
event: KeyboardEvent, event: KeyboardEvent,
key: KeyboardKey, key: KeyboardKey | string,
modifiers: KeyboardModifier[] = [] modifiers: KeyboardModifier[] = []
) { ) {
const eventModifiers = this.modifiersForEvent(event); const eventModifiers = this.modifiersForEvent(event);

View File

@@ -1,7 +1,7 @@
import { WebApplication } from './../application'; import { WebApplication } from './../application';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull'; import pull from 'lodash/pull';
import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs } from 'snjs'; import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs, ContentType } from 'snjs';
export enum AppStateEvent { export enum AppStateEvent {
TagChanged = 1, TagChanged = 1,
@@ -48,6 +48,7 @@ export class AppState {
this.application = application; this.application = application;
this.registerVisibilityObservers(); this.registerVisibilityObservers();
this.addAppEventObserver(); this.addAppEventObserver();
this.streamNotesAndTags();
const onVisibilityChange = () => { const onVisibilityChange = () => {
const visible = document.visibilityState === "visible"; const visible = document.visibilityState === "visible";
@@ -73,6 +74,27 @@ export class AppState {
this.onVisibilityChange = undefined; this.onVisibilityChange = undefined;
} }
streamNotesAndTags() {
this.application!.streamItems(
[ContentType.Note, ContentType.Tag],
async (items) => {
if(this.selectedNote) {
const matchingNote = items.find((candidate) => candidate.uuid === this.selectedNote!.uuid);
if(matchingNote) {
this.selectedNote = matchingNote as SNNote;
}
}
if (this.selectedTag) {
const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid);
if (matchingTag) {
this.selectedTag = matchingTag as SNTag;
}
}
}
);
}
addAppEventObserver() { addAppEventObserver() {
this.unsubApp = this.application.addEventObserver(async (eventName) => { this.unsubApp = this.application.addEventObserver(async (eventName) => {
if (eventName === ApplicationEvent.Started) { if (eventName === ApplicationEvent.Started) {
@@ -166,12 +188,20 @@ export class AppState {
} }
} }
/** Returns the tags that are referncing this note */
getNoteTags(note: SNNote) { getNoteTags(note: SNNote) {
return this.application.referencesForItem(note).filter((ref) => { return this.application.referencingForItem(note).filter((ref) => {
return ref.content_type === note.content_type; return ref.content_type === note.content_type;
}) as SNTag[] }) as SNTag[]
} }
/** Returns the notes this tag references */
getTagNotes(tag: SNTag) {
return this.application.referencesForItem(tag).filter((ref) => {
return ref.content_type === tag.content_type;
}) as SNNote[]
}
getSelectedTag() { getSelectedTag() {
return this.selectedTag; return this.selectedTag;
} }

View File

@@ -19,7 +19,7 @@ export interface PasswordWizardScope extends Partial<ng.IScope> {
} }
export type PanelPuppet = { export type PanelPuppet = {
onReady: () => void onReady?: () => void
ready?: boolean ready?: boolean
setWidth?: (width: number) => void setWidth?: (width: number) => void
setLeft?: (left: number) => void setLeft?: (left: number) => void

View File

@@ -112,8 +112,8 @@
ng-class="{'selected' : self.state.selectedNote == note}" ng-class="{'selected' : self.state.selectedNote == note}"
ng-click='self.selectNote(note, true)' ng-click='self.selectNote(note, true)'
) )
.note-flags(ng-show='note.flags.length > 0') .note-flags(ng-show='self.noteFlags[note.uuid].length > 0')
.flag(ng-class='flag.class', ng-repeat='flag in note.flags') .flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]')
.label {{flag.text}} .label {{flag.text}}
.name(ng-show='note.title') .name(ng-show='note.title')
| {{note.title}} | {{note.title}}
@@ -135,11 +135,9 @@
) {{note.text}} ) {{note.text}}
.date.faded(ng-show='!self.state.hideDate') .date.faded(ng-show='!self.state.hideDate')
span(ng-show="self.state.sortBy == 'client_updated_at'") span(ng-show="self.state.sortBy == 'client_updated_at'")
| Modified {{note.cachedUpdatedAtString || 'Now'}} | Modified {{note.updatedAtString || 'Now'}}
span(ng-show="self.state.sortBy != 'client_updated_at'") span(ng-show="self.state.sortBy != 'client_updated_at'")
| {{note.cachedCreatedAtString || 'Now'}} | {{note.createdAtString || 'Now'}}
.tags-string(ng-show='note.shouldShowTags')
.faded {{note.savedTagsString || note.tagsString()}}
panel-resizer( panel-resizer(
collapsable="true" collapsable="true"
control="self.panelPuppet" control="self.panelPuppet"