feat: starred notes (#1813)

This commit is contained in:
Mo
2022-10-17 10:22:58 -05:00
committed by GitHub
parent 0082a76b19
commit d52fdae0b3
10 changed files with 84 additions and 18 deletions

View File

@@ -13,6 +13,7 @@ export interface ItemContent {
trashed?: boolean
pinned?: boolean
archived?: boolean
starred?: boolean
locked?: boolean
appData?: AppData
}

View File

@@ -21,6 +21,7 @@ export class DecryptedItem<C extends ItemContent = ItemContent>
public readonly pinned: boolean = false
public readonly archived: boolean = false
public readonly locked: boolean = false
public readonly starred: boolean = false
constructor(payload: DecryptedPayloadInterface<C>) {
super(payload)
@@ -32,6 +33,7 @@ export class DecryptedItem<C extends ItemContent = ItemContent>
this.updatedAtString = dateToLocalizedString(this.userModifiedDate)
this.protected = useBoolean(this.payload.content.protected, false)
this.trashed = useBoolean(this.payload.content.trashed, false)
this.starred = useBoolean(this.payload.content.starred, false)
this.pinned = this.getAppDomainValueWithDefault(AppDataField.Pinned, false)
this.archived = this.getAppDomainValueWithDefault(AppDataField.Archived, false)
this.locked = this.getAppDomainValueWithDefault(AppDataField.Locked, false)

View File

@@ -22,6 +22,7 @@ export interface DecryptedItemInterface<C extends ItemContent = ItemContent>
readonly pinned: boolean
readonly archived: boolean
readonly locked: boolean
readonly starred: boolean
readonly userModifiedDate: Date
readonly references: ContentReference[]

View File

@@ -78,6 +78,10 @@ export class DecryptedItemMutator<C extends ItemContent = ItemContent> extends I
this.mutableContent.trashed = trashed
}
set starred(starred: boolean) {
this.mutableContent.starred = starred
}
public set pinned(pinned: boolean) {
this.setAppDataItem(AppDataField.Pinned, pinned)
}

View File

@@ -12,6 +12,7 @@ export enum SystemViewId {
ArchivedNotes = 'archived-notes',
TrashedNotes = 'trashed-notes',
UntaggedNotes = 'untagged-notes',
StarredNotes = 'starred-notes',
}
export interface SmartViewContent extends ItemContent {

View File

@@ -74,10 +74,22 @@ export function BuildSmartViews(
}),
)
const starred = new SmartView(
new DecryptedPayload({
uuid: SystemViewId.StarredNotes,
content_type: ContentType.SmartView,
...PayloadTimestampDefaults(),
content: FillItemContent<SmartViewContent>({
title: 'Starred',
predicate: starredNotesPredicate(options).toJson(),
}),
}),
)
if (supportsFileNavigation) {
return [notes, files, archived, trash, untagged]
return [notes, starred, files, archived, trash, untagged]
} else {
return [notes, archived, trash, untagged]
return [notes, starred, archived, trash, untagged]
}
}
@@ -177,3 +189,22 @@ function untaggedNotesPredicate(options: FilterDisplayOptions) {
return predicate
}
function starredNotesPredicate(options: FilterDisplayOptions) {
const subPredicates: Predicate<SNNote>[] = [
new Predicate('starred', '=', true),
new Predicate('content_type', '=', ContentType.Note),
]
if (options.includeTrashed === false) {
subPredicates.push(new Predicate('trashed', '=', false))
}
if (options.includeProtected === false) {
subPredicates.push(new Predicate('protected', '=', false))
}
if (options.includePinned === false) {
subPredicates.push(new Predicate('pinned', '=', false))
}
const predicate = new CompoundPredicate('and', subPredicates)
return predicate
}

View File

@@ -8,6 +8,7 @@ type Props = {
trashed: ListableContentItem['trashed']
archived: ListableContentItem['archived']
pinned: ListableContentItem['pinned']
starred: ListableContentItem['starred']
}
hasFiles?: boolean
}
@@ -40,6 +41,11 @@ const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false })
<Icon ariaLabel="Files" type="attachment-file" className="text-info" size="small" />
</span>
)}
{item.starred && (
<span className="ml-1.5 flex items-center" title="Starred">
<Icon ariaLabel="Starred" type="star-filled" className="text-warning" size="small" />
</span>
)}
</div>
)
}

View File

@@ -201,6 +201,7 @@ const NotesOptions = ({
const notTrashed = notes.some((note) => !note.trashed)
const pinned = notes.some((note) => note.pinned)
const unpinned = notes.some((note) => !note.pinned)
const starred = notes.some((note) => note.starred)
const editorForNote = useMemo(
() => (notes[0] ? application.componentManager.editorForNote(notes[0]) : undefined),
@@ -330,6 +331,17 @@ const NotesOptions = ({
linkingController={linkingController}
/>
)}
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item"
onClick={() => {
notesController.setStarSelectedNotes(!starred)
}}
>
<Icon type="star" className={iconClass} />
{starred ? 'Unstar' : 'Star'}
</button>
{unpinned && (
<button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item"

View File

@@ -27,23 +27,24 @@ const PADDING_BASE_PX = 14
const PADDING_PER_LEVEL_PX = 21
const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
if (view.uuid === SystemViewId.AllNotes) {
return isSelected ? 'notes-filled' : 'notes'
}
if (view.uuid === SystemViewId.Files) {
return 'folder'
}
if (view.uuid === SystemViewId.ArchivedNotes) {
return 'archive'
}
if (view.uuid === SystemViewId.TrashedNotes) {
return 'trash'
}
if (view.uuid === SystemViewId.UntaggedNotes) {
return 'hashtag-off'
const mapping: Record<SystemViewId, IconType> = {
[SystemViewId.AllNotes]: isSelected ? 'notes-filled' : 'notes',
[SystemViewId.Files]: 'folder',
[SystemViewId.ArchivedNotes]: 'archive',
[SystemViewId.TrashedNotes]: 'trash',
[SystemViewId.UntaggedNotes]: 'hashtag-off',
[SystemViewId.StarredNotes]: 'star-filled',
}
return 'hashtag'
return mapping[view.uuid as SystemViewId] || 'hashtag'
}
const getIconClass = (view: SmartView, isSelected: boolean): string => {
const mapping: Partial<Record<SystemViewId, string>> = {
[SystemViewId.StarredNotes]: 'text-warning',
}
return mapping[view.uuid as SystemViewId] || (isSelected ? 'text-info' : 'text-neutral')
}
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
@@ -108,6 +109,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
const isFaded = false
const iconType = smartViewIconType(view, isSelected)
const iconClass = getIconClass(view, isSelected)
return (
<>
@@ -122,7 +124,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
>
<div className="tag-info">
<div className={'tag-icon mr-2'}>
<Icon type={iconType} className={isSelected ? 'text-info' : 'text-neutral'} />
<Icon type={iconType} className={iconClass} />
</div>
{isEditing ? (
<input

View File

@@ -259,6 +259,12 @@ export class NotesController extends AbstractViewController {
}).catch(console.error)
}
setStarSelectedNotes(starred: boolean): void {
this.changeSelectedNotes((mutator) => {
mutator.starred = starred
}).catch(console.error)
}
async setArchiveSelectedNotes(archived: boolean): Promise<void> {
if (this.getSelectedNotesList().some((note) => note.locked)) {
this.application.alertService