feat: New 'What's New' section in Preferences (#2049) (skip e2e)

This commit is contained in:
Mo
2022-11-24 05:46:44 -06:00
committed by GitHub
parent c40b17bd4c
commit 0a01ddb430
31 changed files with 10100 additions and 23 deletions

View File

@@ -29,6 +29,7 @@ import { DesktopManager } from './Device/DesktopManager'
import {
ArchiveManager,
AutolockService,
ChangelogService,
KeyboardService,
PreferenceId,
RouteService,
@@ -97,6 +98,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
? new DesktopManager(this, deviceInterface)
: undefined
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
this.webServices.changelogService = new ChangelogService(this.environment, this.storage)
if (this.isNativeMobileWeb()) {
this.mobileWebReceiver = new MobileWebReceiver(this)
@@ -193,6 +195,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
return this.webServices.viewControllerManager.paneController
}
public get changelogService() {
return this.webServices.changelogService
}
public get desktopDevice(): DesktopDeviceInterface | undefined {
if (isDesktopDevice(this.deviceInterface)) {
return this.deviceInterface

View File

@@ -1,6 +1,12 @@
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { DesktopManager } from './Device/DesktopManager'
import { ArchiveManager, AutolockService, KeyboardService, ThemeManager } from '@standardnotes/ui-services'
import {
ArchiveManager,
AutolockService,
ChangelogServiceInterface,
KeyboardService,
ThemeManager,
} from '@standardnotes/ui-services'
export type WebServices = {
viewControllerManager: ViewControllerManager
@@ -9,4 +15,5 @@ export type WebServices = {
archiveService: ArchiveManager
themeService: ThemeManager
keyboardService: KeyboardService
changelogService: ChangelogServiceInterface
}

View File

@@ -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

View File

@@ -0,0 +1 @@
export const IgnoreScopes = ['mobile:', 'dev:']

View File

@@ -0,0 +1 @@
export type SectionKey = 'Bug Fixes' | 'Features'

View File

@@ -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

View File

@@ -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
}

View File

@@ -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' },