Use SNJS's built-in display sorting and filtering

This commit is contained in:
Mo Bitar
2020-04-18 21:54:07 -05:00
parent 851269200b
commit 84de39375c
7 changed files with 381 additions and 398 deletions

View File

@@ -38,7 +38,10 @@ export class EditorGroup {
}
closeActiveEditor() {
this.deleteEditor(this.editors[0]);
const activeEditor = this.activeEditor;
if(activeEditor) {
this.deleteEditor(activeEditor);
}
}
closeAllEditors() {

View File

@@ -9,7 +9,8 @@ import {
SNTheme,
ComponentArea,
ComponentAction,
topLevelCompare
topLevelCompare,
CollectionSort
} from 'snjs';
import template from './footer-view.pug';
import { AppStateEvent, EventSource } from '@/ui_models/app_state';
@@ -44,6 +45,7 @@ class FooterViewCtrl extends PureViewCtrl {
private backupStatus?: FooterStatus
private offline = true
private showAccountMenu = false
private didCheckForOffline = false
private queueExtReload = false
private reloadInProgress = false
public hasError = false
@@ -179,8 +181,11 @@ class FooterViewCtrl extends PureViewCtrl {
outOfSync: false
});
} else if (eventName === ApplicationEvent.CompletedSync) {
if (this.offline && this.application!.getNoteCount() === 0) {
this.showAccountMenu = true;
if (!this.didCheckForOffline) {
this.didCheckForOffline = true;
if (this.offline && this.application!.getNoteCount() === 0) {
this.showAccountMenu = true;
}
}
this.syncUpdated();
this.findErrors();
@@ -192,6 +197,18 @@ class FooterViewCtrl extends PureViewCtrl {
}
streamItems() {
this.application.setDisplayOptions(
ContentType.Theme,
CollectionSort.Title,
'asc',
(theme: SNTheme) => {
return (
theme.package_info &&
theme.package_info.dock_icon
);
}
)
this.application!.streamItems(
ContentType.Component,
async () => {
@@ -210,16 +227,7 @@ class FooterViewCtrl extends PureViewCtrl {
ContentType.Theme,
async () => {
const themes = this.application!.getDisplayableItems(ContentType.Theme) as SNTheme[];
const filteredThemes = themes.filter((candidate) => {
return (
!candidate.deleted &&
candidate.package_info &&
candidate.package_info.dock_icon
);
}).sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
this.themesWithIcons = filteredThemes;
this.themesWithIcons = themes;
this.reloadDockShortcuts();
}
);
@@ -233,7 +241,7 @@ class FooterViewCtrl extends PureViewCtrl {
actionHandler: (component, action, data) => {
if (action === ComponentAction.SetSize) {
/** Do comparison to avoid repetitive calls by arbitrary component */
if(!topLevelCompare(component.getLastSize(), data)) {
if (!topLevelCompare(component.getLastSize(), data)) {
this.application!.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.setLastSize(data);

View File

@@ -2,64 +2,43 @@ import { SNNote, SNTag } from 'snjs';
export enum NoteSortKey {
CreatedAt = 'created_at',
UpdatedAt = 'updated_at',
ClientUpdatedAt = 'client_updated_at',
UserUpdatedAt = 'userModifiedDate',
Title = 'title',
/** @legacy Use UserUpdatedAt instead */
UpdatedAt = 'updated_at',
/** @legacy Use UserUpdatedAt instead */
ClientUpdatedAt = 'client_updated_at',
}
export function filterAndSortNotes(
notes: SNNote[],
selectedTag: SNTag,
showArchived: boolean,
hidePinned: boolean,
filterText: string,
sortBy: string,
reverse: boolean,
) {
const filtered = filterNotes(
notes,
selectedTag,
showArchived,
hidePinned,
filterText,
);
const sorted = sortNotes(
filtered,
sortBy,
reverse
);
return sorted;
}
export function filterNotes(
notes: SNNote[],
export function notePassesFilter(
note: SNNote,
selectedTag: SNTag,
showArchived: boolean,
hidePinned: boolean,
filterText: string
) {
let canShowArchived = showArchived;
const canShowPinned = !hidePinned;
if (!selectedTag.isTrashTag && note.trashed) {
return false;
}
const isSmartTag = selectedTag.isSmartTag();
if (isSmartTag) {
canShowArchived = (
canShowArchived ||
selectedTag.isArchiveTag ||
selectedTag.isTrashTag
);
}
if (
(note.archived && !canShowArchived) ||
(note.pinned && !canShowPinned)
) {
return notes.filter((note) => {
let canShowArchived = showArchived;
const canShowPinned = !hidePinned;
if (!selectedTag.isTrashTag && note.trashed) {
return false;
}
const isSmartTag = selectedTag.isSmartTag();
if (isSmartTag) {
canShowArchived = (
canShowArchived ||
selectedTag.isArchiveTag ||
selectedTag.isTrashTag
);
}
if (
(note.archived && !canShowArchived) ||
(note.pinned && !canShowPinned)
) {
return false;
}
return noteMatchesQuery(note, filterText);
});
return false;
}
return noteMatchesQuery(note, filterText);
}
function noteMatchesQuery(
@@ -72,16 +51,13 @@ function noteMatchesQuery(
const title = note.safeTitle().toLowerCase();
const text = note.safeText().toLowerCase();
const lowercaseText = query.toLowerCase();
const quotedText = stringBetweenQuotes(lowercaseText);
if (quotedText) {
return title.includes(quotedText) || text.includes(quotedText);
}
if (stringIsUuid(lowercaseText)) {
return note.uuid === lowercaseText;
}
const words = lowercaseText.split(" ");
const matchesTitle = words.every((word) => {
return title.indexOf(word) >= 0;
@@ -89,7 +65,6 @@ function noteMatchesQuery(
const matchesBody = words.every((word) => {
return text.indexOf(word) >= 0;
});
return matchesTitle || matchesBody;
}
@@ -104,47 +79,4 @@ function stringIsUuid(text: string) {
);
// eslint-disable-next-line no-unneeded-ternary
return matches ? true : false;
}
export function sortNotes(
notes: SNNote[] = [],
sortBy: string,
reverse: boolean
) {
const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => {
if (!pinCheck) {
if (a.pinned && b.pinned) {
return sortValueFn(a, b, true);
}
if (a.pinned) { return -1; }
if (b.pinned) { return 1; }
}
let aValue = (a as any)[sortBy] || '';
let bValue = (b as any)[sortBy] || '';
let vector = 1;
if (reverse) {
vector *= -1;
}
if (sortBy === NoteSortKey.Title) {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
if (aValue.length === 0 && bValue.length === 0) {
return 0;
} else if (aValue.length === 0 && bValue.length !== 0) {
return 1 * vector;
} else if (aValue.length !== 0 && bValue.length === 0) {
return -1 * vector;
} else {
vector *= -1;
}
}
if (aValue > bValue) { return -1 * vector; }
else if (aValue < bValue) { return 1 * vector; }
return 0;
};
const result = notes.sort(function (a, b) {
return sortValueFn(a, b);
});
return result;
}
}

View File

@@ -51,7 +51,7 @@
)
menu-row(
action="self.selectedMenuItem(); self.selectedSortByUpdated()"
circle="self.state.sortBy == 'client_updated_at' && 'success'"
circle="self.state.sortBy == 'userModifiedDate' && 'success'"
desc="'Sort notes with the most recently updated first'"
label="'Date Modified'"
)
@@ -127,9 +127,9 @@
ng-show='!note.preview_html && !note.preview_plain'
) {{note.text}}
.date.faded(ng-show='!self.state.hideDate')
span(ng-show="self.state.sortBy == 'client_updated_at'")
span(ng-show="self.state.sortBy == 'userModifiedDate'")
| Modified {{note.updatedAtString || 'Now'}}
span(ng-show="self.state.sortBy != 'client_updated_at'")
span(ng-show="self.state.sortBy != 'userModifiedDate'")
| {{note.createdAtString || 'Now'}}
panel-resizer(

View File

@@ -9,7 +9,9 @@ import {
SNNote,
SNTag,
WebPrefKey,
findInArray
findInArray,
CollectionSort,
SNSmartTag
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent } from '@/ui_models/app_state';
@@ -19,7 +21,7 @@ import {
} from '@/views/constants';
import {
NoteSortKey,
filterAndSortNotes
notePassesFilter
} from './note_utils';
import { UuidString } from '@/../../../../snjs/dist/@types/types';
@@ -207,8 +209,11 @@ class NotesViewCtrl extends PureViewCtrl {
streamNotesAndTags() {
this.application!.streamItems(
[ContentType.Note, ContentType.Tag],
[ContentType.Note],
async (items) => {
const notes = items as SNNote[];
/** If a note changes, it will be queried against the existing filter;
* we dont need to reload display options */
await this.reloadNotes();
const activeNote = this.activeEditorNote;
if (activeNote) {
@@ -220,16 +225,24 @@ class NotesViewCtrl extends PureViewCtrl {
this.selectFirstNote();
}
/** Note has changed values, reset its flags */
const notes = items.filter((item) => {
return item.content_type === ContentType.Note
}) as SNNote[];
for (const note of notes) {
if (note.deleted) {
continue;
}
this.loadFlagsForNote(note);
}
if (findInArray(items, 'uuid', this.appState.selectedTag?.uuid)) {
}
);
this.application!.streamItems(
[ContentType.Tag],
async (items) => {
const tags = items as SNTag[];
/** A tag could have changed its relationships, so we need to reload the filter */
this.reloadNotesDisplayOptions();
await this.reloadNotes();
if (findInArray(tags, 'uuid', this.appState.selectedTag?.uuid)) {
/** Tag title could have changed */
this.reloadPanelTitle();
}
}
@@ -258,6 +271,7 @@ class NotesViewCtrl extends PureViewCtrl {
/* Capture db load state before beginning reloadNotes,
since this status may change during reload */
const dbLoaded = this.application!.isDatabaseLoaded();
this.reloadNotesDisplayOptions();
await this.reloadNotes();
if (this.getState().notes!.length > 0) {
@@ -296,21 +310,38 @@ class NotesViewCtrl extends PureViewCtrl {
return this.reloadNotesPromise;
}
/**
* Note that reloading display options destroys the current index and rebuilds it,
* so call sparingly. The runtime complexity of destroying and building
* an index is roughly O(n^2).
*/
private reloadNotesDisplayOptions() {
const tag = this.appState.selectedTag!;
this.application.setDisplayOptions(
ContentType.Note,
this.getState().sortBy! as CollectionSort,
this.getState().sortReverse! ? 'asc' : 'dsc',
(note: SNNote) => {
const matchesTag = tag.isSmartTag()
? note.satisfiesPredicate((tag as SNSmartTag).predicate)
: tag.hasRelationshipWithItem(note);
return matchesTag && notePassesFilter(
note,
this.appState.selectedTag!,
this.getState().showArchived!,
this.getState().hidePinned!,
this.getState().noteFilter.text.toLowerCase()
)
}
)
}
private async performPeloadNotes() {
const tag = this.appState.selectedTag!;
if (!tag) {
return;
}
const tagNotes = this.appState.getTagNotes(tag);
const notes = filterAndSortNotes(
tagNotes,
tag,
this.getState().showArchived!,
this.getState().hidePinned!,
this.getState().noteFilter.text.toLowerCase(),
this.getState().sortBy!,
this.getState().sortReverse!
);
const notes = this.application.getDisplayableItems(ContentType.Note) as SNNote[];
for (const note of notes) {
if (note.errorDecrypting) {
this.loadFlagsForNote(note);
@@ -351,9 +382,9 @@ class NotesViewCtrl extends PureViewCtrl {
WebPrefKey.SortNotesBy,
NoteSortKey.CreatedAt
);
if (sortBy === NoteSortKey.UpdatedAt) {
/** Use client_updated_at instead */
sortBy = NoteSortKey.ClientUpdatedAt;
if (sortBy === NoteSortKey.UpdatedAt || sortBy === NoteSortKey.ClientUpdatedAt) {
/** Use UserUpdatedAt instead */
sortBy = NoteSortKey.UserUpdatedAt;
}
viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.application!.getPrefsService().getValue(
@@ -391,6 +422,7 @@ class NotesViewCtrl extends PureViewCtrl {
);
}
}
this.reloadNotesDisplayOptions();
await this.reloadNotes();
if (prevSortValue && prevSortValue !== sortBy) {
this.selectFirstNote();
@@ -449,11 +481,11 @@ class NotesViewCtrl extends PureViewCtrl {
optionsSubtitle() {
let base = "";
if (this.getState().sortBy === 'created_at') {
if (this.getState().sortBy === NoteSortKey.CreatedAt) {
base += " Date Added";
} else if (this.getState().sortBy === 'client_updated_at') {
} else if (this.getState().sortBy === NoteSortKey.UserUpdatedAt) {
base += " Date Modified";
} else if (this.getState().sortBy === 'title') {
} else if (this.getState().sortBy === NoteSortKey.Title) {
base += " Title";
}
if (this.getState().showArchived) {
@@ -611,6 +643,7 @@ class NotesViewCtrl extends PureViewCtrl {
if (this.searchSubmitted) {
this.searchSubmitted = false;
}
this.reloadNotesDisplayOptions();
await this.reloadNotes();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long