mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
feat(frontend): show indicator in document title for background changes
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
336e621bc4
commit
9497726a7c
5 changed files with 142 additions and 2 deletions
|
@ -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')
|
||||||
|
})
|
||||||
|
})
|
|
@ -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
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
24
frontend/src/hooks/common/use-is-document-visible.spec.tsx
Normal file
24
frontend/src/hooks/common/use-is-document-visible.spec.tsx
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
28
frontend/src/hooks/common/use-is-document-visible.ts
Normal file
28
frontend/src/hooks/common/use-is-document-visible.ts
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue