feat: Add current Writing Streak to Daily Notebooks (#2093)
This commit is contained in:
@@ -687,3 +687,7 @@ export function useBoolean(value: boolean | undefined, defaultValue: boolean): b
|
|||||||
export function spaceSeparatedStrings(...strings: string[]): string {
|
export function spaceSeparatedStrings(...strings: string[]): string {
|
||||||
return strings.join(' ')
|
return strings.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pluralize(count: number, singular: string, plural: string): string {
|
||||||
|
return count === 1 ? singular : plural
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { FunctionComponent } from 'react'
|
|||||||
|
|
||||||
const DotOrgNotice: FunctionComponent = () => {
|
const DotOrgNotice: FunctionComponent = () => {
|
||||||
return (
|
return (
|
||||||
<div className="align-center z-modal flex h-30 w-full w-full items-center bg-danger text-center text-info-contrast">
|
<div className="z-modal flex h-30 w-full w-full items-center bg-danger text-center text-info-contrast">
|
||||||
<div className="w-full text-center text-xl font-bold">
|
<div className="w-full text-center text-xl font-bold">
|
||||||
app.standardnotes.org is no longer maintained. Please switch to{' '}
|
app.standardnotes.org is no longer maintained. Please switch to{' '}
|
||||||
<a className="underline" href="https://app.standardnotes.com">
|
<a className="underline" href="https://app.standardnotes.com">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Props = {
|
|||||||
selectedDay?: Date
|
selectedDay?: Date
|
||||||
selectedDayType?: 'item' | 'template'
|
selectedDayType?: 'item' | 'template'
|
||||||
className?: string
|
className?: string
|
||||||
|
children?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InfiniteCalendarInterface = {
|
export type InfiniteCalendarInterface = {
|
||||||
@@ -27,7 +28,7 @@ export type InfiniteCalendarInterface = {
|
|||||||
const PageSize = 2
|
const PageSize = 2
|
||||||
|
|
||||||
const InfiniteCalendar = forwardRef<InfiniteCalendarInterface, Props>(
|
const InfiniteCalendar = forwardRef<InfiniteCalendarInterface, Props>(
|
||||||
({ activities, onDateSelect, selectedDay, className }: Props, ref) => {
|
({ activities, onDateSelect, selectedDay, className, children }: Props, ref) => {
|
||||||
const [expanded, setExpanded] = useState(isMobileScreen() ? false : true)
|
const [expanded, setExpanded] = useState(isMobileScreen() ? false : true)
|
||||||
const [restoreScrollAfterExpand, setRestoreScrollAfterExpand] = useState(false)
|
const [restoreScrollAfterExpand, setRestoreScrollAfterExpand] = useState(false)
|
||||||
const scrollerRef = useRef<InfiniteScrollerInterface | null>(null)
|
const scrollerRef = useRef<InfiniteScrollerInterface | null>(null)
|
||||||
@@ -216,6 +217,7 @@ const InfiniteCalendar = forwardRef<InfiniteCalendarInterface, Props>(
|
|||||||
})}
|
})}
|
||||||
</InfinteScroller>
|
</InfinteScroller>
|
||||||
)}
|
)}
|
||||||
|
{expanded && children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { AppPaneId } from '../../Panes/AppPaneMetadata'
|
|||||||
import { createDailyItemsWithToday, createItemsByDateMapping, insertBlanks } from './CreateDailySections'
|
import { createDailyItemsWithToday, createItemsByDateMapping, insertBlanks } from './CreateDailySections'
|
||||||
import { DailyItemsDay } from './DailyItemsDaySection'
|
import { DailyItemsDay } from './DailyItemsDaySection'
|
||||||
import { DailyItemCell } from './DailyItemCell'
|
import { DailyItemCell } from './DailyItemCell'
|
||||||
import { SNTag } from '@standardnotes/snjs'
|
import { SNTag, pluralize } from '@standardnotes/snjs'
|
||||||
import { CalendarActivity } from '../Calendar/CalendarActivity'
|
import { CalendarActivity } from '../Calendar/CalendarActivity'
|
||||||
import { dateToDailyDayIdentifier } from './Utils'
|
import { dateToDailyDayIdentifier, getDailyWritingStreak } from './Utils'
|
||||||
import InfiniteCalendar, { InfiniteCalendarInterface } from '../Calendar/InfiniteCalendar'
|
import InfiniteCalendar, { InfiniteCalendarInterface } from '../Calendar/InfiniteCalendar'
|
||||||
import { InfiniteScrollerInterface, InfinteScroller } from '../InfiniteScroller/InfiniteScroller'
|
import { InfiniteScrollerInterface, InfinteScroller } from '../InfiniteScroller/InfiniteScroller'
|
||||||
import { LoggingDomain, log } from '@/Logging'
|
import { LoggingDomain, log } from '@/Logging'
|
||||||
@@ -50,6 +50,11 @@ const DailyContentList: FunctionComponent<Props> = ({
|
|||||||
return createItemsByDateMapping(items)
|
return createItemsByDateMapping(items)
|
||||||
}, [items])
|
}, [items])
|
||||||
|
|
||||||
|
const currentStreak = useMemo(
|
||||||
|
() => getDailyWritingStreak(todayItem, itemsByDateMapping),
|
||||||
|
[todayItem, itemsByDateMapping],
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTodayItem(dailyItems.find((item) => item.isToday) as DailyItemsDay)
|
setTodayItem(dailyItems.find((item) => item.isToday) as DailyItemsDay)
|
||||||
}, [dailyItems])
|
}, [dailyItems])
|
||||||
@@ -182,7 +187,16 @@ const DailyContentList: FunctionComponent<Props> = ({
|
|||||||
selectedDayType={!selectedDay ? undefined : hasItemsOnSelectedDay ? 'item' : 'template'}
|
selectedDayType={!selectedDay ? undefined : hasItemsOnSelectedDay ? 'item' : 'template'}
|
||||||
ref={calendarRef}
|
ref={calendarRef}
|
||||||
className={'flex-column flex'}
|
className={'flex-column flex'}
|
||||||
/>
|
>
|
||||||
|
{currentStreak > 0 && (
|
||||||
|
<div className="flex w-full items-center justify-center border-t border-solid border-border bg-secondary-background p-2">
|
||||||
|
<span className="opacity-50">Current Streak</span>
|
||||||
|
<span className="ml-1.5 font-bold">
|
||||||
|
{currentStreak} {pluralize(currentStreak, 'Day', 'Days')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</InfiniteCalendar>
|
||||||
|
|
||||||
<InfinteScroller
|
<InfinteScroller
|
||||||
paginateFront={paginateTop}
|
paginateFront={paginateTop}
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
|
import { addDaysToDate } from '@standardnotes/utils'
|
||||||
|
import { ListableContentItem } from '../Types/ListableContentItem'
|
||||||
|
import { DailyItemsDay } from './DailyItemsDaySection'
|
||||||
|
|
||||||
export function dateToDailyDayIdentifier(date: Date): string {
|
export function dateToDailyDayIdentifier(date: Date): string {
|
||||||
return date.toLocaleDateString()
|
return date.toLocaleDateString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDailyWritingStreak(
|
||||||
|
todayItem: DailyItemsDay | undefined,
|
||||||
|
itemsByDateMapping: Record<string, ListableContentItem[]>,
|
||||||
|
) {
|
||||||
|
if (!todayItem) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDay = todayItem.date
|
||||||
|
let checkingDayOffsetFromToday = -1
|
||||||
|
let keepLooping = true
|
||||||
|
let streak = 0
|
||||||
|
|
||||||
|
while (keepLooping) {
|
||||||
|
const checkingDay = addDaysToDate(startDay, checkingDayOffsetFromToday)
|
||||||
|
const items = itemsByDateMapping[dateToDailyDayIdentifier(checkingDay)]
|
||||||
|
if (!items || items?.length === 0) {
|
||||||
|
keepLooping = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
streak++
|
||||||
|
checkingDayOffsetFromToday--
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasEntryForToday = itemsByDateMapping[dateToDailyDayIdentifier(todayItem.date)]?.length > 0
|
||||||
|
|
||||||
|
return streak + (hasEntryForToday ? 1 : 0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
|||||||
>
|
>
|
||||||
<BlocksEditor
|
<BlocksEditor
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
ignoreFirstChange={false}
|
ignoreFirstChange={controller.isTemplateNote}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'relative h-full resize-none px-4 py-4 focus:shadow-none focus:outline-none',
|
'relative h-full resize-none px-4 py-4 focus:shadow-none focus:outline-none',
|
||||||
lineHeight && `leading-${lineHeight.toLowerCase()}`,
|
lineHeight && `leading-${lineHeight.toLowerCase()}`,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const WhatsNew = ({ application }: { application: WebApplication }) => {
|
|||||||
|
|
||||||
if (!changelog) {
|
if (!changelog) {
|
||||||
return (
|
return (
|
||||||
<div className="align-center flex h-full w-full items-center text-center">
|
<div className="flex h-full w-full items-center text-center">
|
||||||
<span className="w-full font-bold">Loading...</span>
|
<span className="w-full font-bold">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ module.exports = {
|
|||||||
foreground: 'var(--sn-stylekit-foreground-color)',
|
foreground: 'var(--sn-stylekit-foreground-color)',
|
||||||
contrast: 'var(--sn-stylekit-contrast-background-color)',
|
contrast: 'var(--sn-stylekit-contrast-background-color)',
|
||||||
'secondary-contrast': 'var(--sn-stylekit-secondary-contrast-border-color)',
|
'secondary-contrast': 'var(--sn-stylekit-secondary-contrast-border-color)',
|
||||||
|
'secondary-background': 'var(--sn-stylekit-secondary-background-color)',
|
||||||
text: 'var(--sn-stylekit-contrast-foreground-color)',
|
text: 'var(--sn-stylekit-contrast-foreground-color)',
|
||||||
border: 'var(--sn-stylekit-border-color)',
|
border: 'var(--sn-stylekit-border-color)',
|
||||||
'secondary-border': 'var(--sn-stylekit-secondary-border-color)',
|
'secondary-border': 'var(--sn-stylekit-secondary-border-color)',
|
||||||
|
|||||||
Reference in New Issue
Block a user