feat: New 'What's New' section in Preferences (#2049) (skip e2e)
This commit is contained in:
@@ -9,6 +9,7 @@ import Security from './Panes/Security/Security'
|
||||
import Listed from './Panes/Listed/Listed'
|
||||
import HelpAndFeedback from './Panes/HelpFeedback'
|
||||
import { PreferencesProps } from './PreferencesProps'
|
||||
import WhatsNew from './Panes/WhatsNew/WhatsNew'
|
||||
|
||||
const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = ({
|
||||
menu,
|
||||
@@ -51,6 +52,8 @@ const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu
|
||||
return null
|
||||
case 'help-feedback':
|
||||
return <HelpAndFeedback application={application} />
|
||||
case 'whats-new':
|
||||
return <WhatsNew application={application} />
|
||||
default:
|
||||
return (
|
||||
<General
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const IgnoreScopes = ['mobile:', 'dev:']
|
||||
@@ -0,0 +1 @@
|
||||
export type SectionKey = 'Bug Fixes' | 'Features'
|
||||
@@ -0,0 +1,109 @@
|
||||
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Changelog } from '@standardnotes/ui-services'
|
||||
import { LinkButton, Subtitle, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { getSectionItems } from './getSectionItems'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import { compareSemVersions } from '@standardnotes/snjs'
|
||||
|
||||
const WhatsNewSection = ({ items, sectionName }: { items: string[] | undefined; sectionName: string }) => {
|
||||
if (!items) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Subtitle>{sectionName}</Subtitle>
|
||||
<ul className="list-inside">
|
||||
{items.map((item, index) => (
|
||||
<li key={index}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const WhatsNew = ({ application }: { application: WebApplication }) => {
|
||||
const [changelog, setChangelog] = useState<Changelog | null>(null)
|
||||
|
||||
const appVersion = application.version
|
||||
const lastReadVersion = useMemo(() => application.changelogService.getLastReadVersion(), [application])
|
||||
|
||||
useEffect(() => {
|
||||
void application.changelogService.getChangelog().then(setChangelog)
|
||||
}, [application])
|
||||
|
||||
useEffect(() => {
|
||||
if (changelog) {
|
||||
application.changelogService.markAsRead()
|
||||
}
|
||||
}, [changelog, application])
|
||||
|
||||
if (!changelog) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesPane>
|
||||
{changelog.versions.map((version, index) => {
|
||||
const bugFixes = getSectionItems(version, 'Bug Fixes')
|
||||
const features = getSectionItems(version, 'Features')
|
||||
|
||||
if (!bugFixes && !features) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!version.version) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isUnreadVersion = lastReadVersion && compareSemVersions(version.version, lastReadVersion) > 0
|
||||
|
||||
const isLatest = index === 0
|
||||
const isDesktopEnvironment = isDesktopApplication()
|
||||
const showDownloadLink = isDesktopEnvironment && isLatest
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<div key={version.version}>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-start">
|
||||
<Title className="mb-3 flex">{version.version}</Title>
|
||||
{version.version === appVersion && (
|
||||
<div className="ml-2 rounded bg-info px-2 py-1 text-[10px] font-bold text-info-contrast">
|
||||
Your Version
|
||||
</div>
|
||||
)}
|
||||
{isLatest && (
|
||||
<div className="ml-2 rounded bg-success px-2 py-1 text-[10px] font-bold text-success-contrast">
|
||||
Latest Version
|
||||
</div>
|
||||
)}
|
||||
{isUnreadVersion && (
|
||||
<div className="ml-2 rounded bg-success px-2 py-1 text-[10px] font-bold text-success-contrast">
|
||||
New
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showDownloadLink && (
|
||||
<LinkButton
|
||||
label={'Open Downloads'}
|
||||
link={application.changelogService.getDesktopDownloadsUrl(version.version)}
|
||||
className="mb-3"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<WhatsNewSection sectionName="Features" items={features} />
|
||||
{features && bugFixes && <HorizontalSeparator classes="my-4" />}
|
||||
<WhatsNewSection sectionName="Bug Fixes" items={bugFixes} />
|
||||
</div>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})}
|
||||
</PreferencesPane>
|
||||
)
|
||||
}
|
||||
|
||||
export default WhatsNew
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ChangelogVersion } from '@standardnotes/ui-services'
|
||||
import { SectionKey } from './SectionKey'
|
||||
import { IgnoreScopes } from './IgnoreScopes'
|
||||
|
||||
function removeAnythingInParentheses(line: string): string {
|
||||
return line.replace(/\(.*\)/g, '')
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(line: string): string {
|
||||
return line.charAt(0).toUpperCase() + line.slice(1)
|
||||
}
|
||||
|
||||
export function formatChangelogLine(line: string): string {
|
||||
let result = capitalizeFirstLetter(line)
|
||||
|
||||
result = removeAnythingInParentheses(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function lineHasIgnoredScope(line: string): boolean {
|
||||
return IgnoreScopes.some((scope) => line.toLowerCase().includes(scope.toLowerCase()))
|
||||
}
|
||||
|
||||
function lineHasOnlyOneWord(line: string): boolean {
|
||||
return line.trim().split(' ').length === 1
|
||||
}
|
||||
|
||||
export function getSectionItems(version: ChangelogVersion, sectionKey: SectionKey): string[] | undefined {
|
||||
const section = version.parsed[sectionKey]
|
||||
if (!section) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const filtered = section.map(formatChangelogLine).filter((item) => {
|
||||
return !lineHasIgnoredScope(item) && !lineHasOnlyOneWord(item)
|
||||
})
|
||||
|
||||
if (filtered.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -20,6 +20,7 @@ interface SelectableMenuItem extends PreferencesMenuItem {
|
||||
* Items are in order of appearance
|
||||
*/
|
||||
const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
||||
{ id: 'whats-new', label: "What's New", icon: 'asterisk' },
|
||||
{ id: 'account', label: 'Account', icon: 'user' },
|
||||
{ id: 'general', label: 'General', icon: 'settings' },
|
||||
{ id: 'security', label: 'Security', icon: 'security' },
|
||||
@@ -33,6 +34,7 @@ const PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
||||
]
|
||||
|
||||
const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
||||
{ id: 'whats-new', label: "What's New", icon: 'asterisk' },
|
||||
{ id: 'account', label: 'Account', icon: 'user' },
|
||||
{ id: 'general', label: 'General', icon: 'settings' },
|
||||
{ id: 'security', label: 'Security', icon: 'security' },
|
||||
|
||||
Reference in New Issue
Block a user