feat(frontend): show indicator in document title for background changes

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-03-28 12:22:52 +02:00 committed by Erik Michelson
parent 336e621bc4
commit 9497726a7c
5 changed files with 142 additions and 2 deletions

View file

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as UseIsDocumentVisibleModule from '../../../../hooks/common/use-is-document-visible'
import * as UseNoteMarkdownContent from '../../../../hooks/common/use-note-markdown-content'
import { useHasMarkdownContentBeenChangedInBackground } from './use-has-markdown-content-been-changed-in-background'
import { render } from '@testing-library/react'
import React, { Fragment } from 'react'
jest.mock('../../../../hooks/common/use-is-document-visible')
jest.mock('../../../../hooks/common/use-note-markdown-content')
describe('use has markdown content been changed in background', () => {
const TestComponent: React.FC = () => {
const visible = useHasMarkdownContentBeenChangedInBackground()
return <Fragment>{String(visible)}</Fragment>
}
let documentVisible = true
let noteContent = 'content'
beforeEach(() => {
jest.spyOn(UseIsDocumentVisibleModule, 'useIsDocumentVisible').mockImplementation(() => documentVisible)
jest.spyOn(UseNoteMarkdownContent, 'useNoteMarkdownContent').mockImplementation(() => noteContent)
})
it('returns the correct value', () => {
documentVisible = true
noteContent = 'content1'
const view = render(<TestComponent />)
expect(view.container.textContent).toBe('false')
expect(view.container.textContent).toBe('false') //intentionally no change
noteContent = 'content2'
view.rerender(<TestComponent />)
expect(view.container.textContent).toBe('false')
documentVisible = false
view.rerender(<TestComponent />)
expect(view.container.textContent).toBe('false')
noteContent = 'content3'
view.rerender(<TestComponent />)
expect(view.container.textContent).toBe('true')
noteContent = 'content2'
view.rerender(<TestComponent />)
expect(view.container.textContent).toBe('true')
documentVisible = true
view.rerender(<TestComponent />)
expect(view.container.textContent).toBe('false')
})
})

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useIsDocumentVisible } from '../../../../hooks/common/use-is-document-visible'
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
import { useEffect, useRef, useState } from 'react'
/**
* Determines if the markdown content has been changed while the browser tab hasn't been active.
*/
export const useHasMarkdownContentBeenChangedInBackground = (): boolean => {
const [backgroundChangesHappened, setBackgroundChangesHappened] = useState(false)
const documentVisible = useIsDocumentVisible()
const currentContent = useNoteMarkdownContent()
const lastContent = useRef<string>('')
useEffect(() => {
if (lastContent.current === currentContent || documentVisible) {
lastContent.current = currentContent
setBackgroundChangesHappened(false)
return
}
lastContent.current = currentContent
setBackgroundChangesHappened(true)
}, [currentContent, documentVisible])
return backgroundChangesHappened
}

View file

@ -5,6 +5,7 @@
*/ */
import { useAppTitle } from '../../../hooks/common/use-app-title' import { useAppTitle } from '../../../hooks/common/use-app-title'
import { useNoteTitle } from '../../../hooks/common/use-note-title' import { useNoteTitle } from '../../../hooks/common/use-note-title'
import { useHasMarkdownContentBeenChangedInBackground } from './hooks/use-has-markdown-content-been-changed-in-background'
import Head from 'next/head' import Head from 'next/head'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
@ -14,10 +15,11 @@ import React, { useMemo } from 'react'
export const NoteAndAppTitleHead: React.FC = () => { export const NoteAndAppTitleHead: React.FC = () => {
const noteTitle = useNoteTitle() const noteTitle = useNoteTitle()
const appTitle = useAppTitle() const appTitle = useAppTitle()
const showDot = useHasMarkdownContentBeenChangedInBackground()
const noteAndAppTitle = useMemo(() => { const noteAndAppTitle = useMemo(() => {
return noteTitle + ' - ' + appTitle return (showDot ? '(•) ' : '') + noteTitle + ' - ' + appTitle
}, [appTitle, noteTitle]) }, [appTitle, noteTitle, showDot])
return ( return (
<Head> <Head>

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useIsDocumentVisible } from './use-is-document-visible'
import { fireEvent, render } from '@testing-library/react'
import React, { Fragment } from 'react'
describe('use is document visible', () => {
const TestComponent: React.FC = () => {
const visible = useIsDocumentVisible()
return <Fragment>{String(visible)}</Fragment>
}
it('returns the correct value', () => {
const view = render(<TestComponent />)
expect(view.container.textContent).toBe('true')
fireEvent(window, new Event('blur'))
expect(view.container.textContent).toBe('false')
fireEvent(window, new Event('focus'))
expect(view.container.textContent).toBe('true')
})
})

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect, useState } from 'react'
/**
* Uses the browsers visiblity API to determine if the tab is active or now.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
*/
export const useIsDocumentVisible = (): boolean => {
const [documentVisible, setDocumentVisible] = useState(true)
useEffect(() => {
const onFocus = () => setDocumentVisible(true)
const onBlur = () => setDocumentVisible(false)
window.addEventListener('focus', onFocus)
window.addEventListener('blur', onBlur)
return () => {
document.removeEventListener('focus', onFocus)
document.removeEventListener('blur', onBlur)
}
}, [])
return documentVisible
}