mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 15:14:56 -04:00
Use "untitled" fallback in history entry without title (#1546)
This commit is contained in:
parent
8096267161
commit
9118c8310b
8 changed files with 129 additions and 53 deletions
|
@ -5,25 +5,84 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe('History', () => {
|
describe('History', () => {
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('/history')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('History Mode', () => {
|
describe('History Mode', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/history')
|
||||||
|
})
|
||||||
|
|
||||||
it('Cards', () => {
|
it('Cards', () => {
|
||||||
cy.get('div.card')
|
cy.get('div.card').should('be.visible')
|
||||||
.should('be.visible')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Table', () => {
|
it('Table', () => {
|
||||||
cy.get('i.fa-table')
|
cy.get('[data-cypress-id="history-mode-table"]').click()
|
||||||
.click()
|
cy.get('[data-cypress-id="history-table"]').should('be.visible')
|
||||||
cy.get('table.history-table')
|
})
|
||||||
.should('be.visible')
|
})
|
||||||
|
|
||||||
|
describe('entry title', () => {
|
||||||
|
describe('is as given when not empty', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.clearLocalStorage('history')
|
||||||
|
cy.intercept('GET', '/mock-backend/api/private/me/history', {
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
identifier: 'cypress',
|
||||||
|
title: 'Features',
|
||||||
|
lastVisited: '2020-05-16T22:26:56.547Z',
|
||||||
|
pinStatus: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
cy.visit('/history')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('in table view', () => {
|
||||||
|
cy.get('[data-cypress-id="history-mode-table"]').click()
|
||||||
|
cy.get('[data-cypress-id="history-table"]').should('be.visible')
|
||||||
|
cy.get('[data-cypress-id="history-entry-title"]').contains('Features')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('in cards view', () => {
|
||||||
|
cy.get('[data-cypress-id="history-entry-title"]').contains('Features')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('is untitled when not empty', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.clearLocalStorage('history')
|
||||||
|
cy.intercept('GET', '/mock-backend/api/private/me/history', {
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
identifier: 'cypress-no-title',
|
||||||
|
title: '',
|
||||||
|
lastVisited: '2020-05-16T22:26:56.547Z',
|
||||||
|
pinStatus: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
cy.visit('/history')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('in table view', () => {
|
||||||
|
cy.get('[data-cypress-id="history-mode-table"]').click()
|
||||||
|
cy.get('[data-cypress-id="history-table"]').should('be.visible')
|
||||||
|
cy.get('[data-cypress-id="history-entry-title"]').contains('Untitled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('in cards view', () => {
|
||||||
|
cy.get('[data-cypress-id="history-entry-title"]').contains('Untitled')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Pinning', () => {
|
describe('Pinning', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/history')
|
||||||
|
})
|
||||||
|
|
||||||
describe('working', () => {
|
describe('working', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('PUT', '/mock-backend/api/private/me/history/features', (req) => {
|
cy.intercept('PUT', '/mock-backend/api/private/me/history/features', (req) => {
|
||||||
|
@ -32,29 +91,17 @@ describe('History', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Cards', () => {
|
it('Cards', () => {
|
||||||
cy.get('div.card')
|
cy.get('div.card').should('be.visible')
|
||||||
.should('be.visible')
|
cy.get('.history-pin.btn').first().as('pin-button')
|
||||||
cy.get('.history-pin.btn')
|
cy.get('@pin-button').should('have.class', 'pinned').click()
|
||||||
.first()
|
cy.get('@pin-button').should('not.have.class', 'pinned')
|
||||||
.as('pin-button')
|
|
||||||
cy.get('@pin-button')
|
|
||||||
.should('have.class', 'pinned')
|
|
||||||
.click()
|
|
||||||
cy.get('@pin-button')
|
|
||||||
.should('not.have.class', 'pinned')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Table', () => {
|
it('Table', () => {
|
||||||
cy.get('i.fa-table')
|
cy.get('i.fa-table').click()
|
||||||
.click()
|
cy.get('.history-pin.btn').first().as('pin-button')
|
||||||
cy.get('.history-pin.btn')
|
cy.get('@pin-button').should('have.class', 'pinned').click()
|
||||||
.first()
|
cy.get('@pin-button').should('not.have.class', 'pinned')
|
||||||
.as('pin-button')
|
|
||||||
cy.get('@pin-button')
|
|
||||||
.should('have.class', 'pinned')
|
|
||||||
.click()
|
|
||||||
cy.get('@pin-button')
|
|
||||||
.should('not.have.class', 'pinned')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -66,23 +113,15 @@ describe('History', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Cards', () => {
|
it('Cards', () => {
|
||||||
cy.get('div.card')
|
cy.get('div.card').should('be.visible')
|
||||||
.should('be.visible')
|
cy.get('.fa-thumb-tack').first().click()
|
||||||
cy.get('.fa-thumb-tack')
|
cy.get('.notifications-area .toast').should('be.visible')
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
cy.get('.notifications-area .toast')
|
|
||||||
.should('be.visible')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Table', () => {
|
it('Table', () => {
|
||||||
cy.get('i.fa-table')
|
cy.get('i.fa-table').click()
|
||||||
.click()
|
cy.get('.fa-thumb-tack').first().click()
|
||||||
cy.get('.fa-thumb-tack')
|
cy.get('.notifications-area .toast').should('be.visible')
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
cy.get('.notifications-area .toast')
|
|
||||||
.should('be.visible')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"identifier": "29QLD0AmT-adevdOPECtqg",
|
"identifier": "29QLD0AmT-adevdOPECtqg",
|
||||||
"title": "HedgeDoc community call 2020-04-26",
|
"title": "",
|
||||||
"lastVisited": "2020-05-16T22:26:56.547Z",
|
"lastVisited": "2020-05-16T22:26:56.547Z",
|
||||||
"pinStatus": false,
|
"pinStatus": false,
|
||||||
"tags": [
|
"tags": [
|
||||||
"HedgeDoc",
|
"empty title",
|
||||||
"Community Call"
|
"should be untitled"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,7 @@ import type { OnWordCountCalculatedMessage } from '../../../render-page/window-p
|
||||||
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
import { CommunicationMessageType } from '../../../render-page/window-post-message-communicator/rendering-message'
|
||||||
import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
import { useEditorReceiveHandler } from '../../../render-page/window-post-message-communicator/hooks/use-editor-receive-handler'
|
||||||
import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready'
|
import { useEffectOnRendererReady } from '../../../render-page/window-post-message-communicator/hooks/use-effect-on-renderer-ready'
|
||||||
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new info line for the document information dialog that holds the
|
* Creates a new info line for the document information dialog that holds the
|
||||||
|
@ -42,7 +43,7 @@ export const DocumentInfoLineWordCount: React.FC = () => {
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<ShowIf condition={wordCount !== null}>
|
<ShowIf condition={wordCount !== null}>
|
||||||
<Trans i18nKey={'editor.modal.documentInfo.words'}>
|
<Trans i18nKey={'editor.modal.documentInfo.words'}>
|
||||||
<UnitalicBoldText text={wordCount ?? ''} data-cypress-id={'document-info-word-count'} />
|
<UnitalicBoldText text={wordCount ?? ''} {...cypressId('document-info-word-count')} />
|
||||||
</Trans>
|
</Trans>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</DocumentInfoLine>
|
</DocumentInfoLine>
|
||||||
|
|
|
@ -14,6 +14,8 @@ import type { HistoryEntryProps, HistoryEventHandlers } from '../history-content
|
||||||
import { PinButton } from '../pin-button/pin-button'
|
import { PinButton } from '../pin-button/pin-button'
|
||||||
import { formatHistoryDate } from '../utils'
|
import { formatHistoryDate } from '../utils'
|
||||||
import './history-card.scss'
|
import './history-card.scss'
|
||||||
|
import { useHistoryEntryTitle } from '../use-history-entry-title'
|
||||||
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
|
||||||
export const HistoryCard: React.FC<HistoryEntryProps & HistoryEventHandlers> = ({
|
export const HistoryCard: React.FC<HistoryEntryProps & HistoryEventHandlers> = ({
|
||||||
entry,
|
entry,
|
||||||
|
@ -29,6 +31,8 @@ export const HistoryCard: React.FC<HistoryEntryProps & HistoryEventHandlers> = (
|
||||||
onDeleteClick(entry.identifier)
|
onDeleteClick(entry.identifier)
|
||||||
}, [onDeleteClick, entry.identifier])
|
}, [onDeleteClick, entry.identifier])
|
||||||
|
|
||||||
|
const entryTitle = useHistoryEntryTitle(entry)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4'>
|
<div className='p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4'>
|
||||||
<Card className='card-min-height' text={'dark'} bg={'light'}>
|
<Card className='card-min-height' text={'dark'} bg={'light'}>
|
||||||
|
@ -38,7 +42,9 @@ export const HistoryCard: React.FC<HistoryEntryProps & HistoryEventHandlers> = (
|
||||||
</div>
|
</div>
|
||||||
<Link to={`/n/${entry.identifier}`} className='text-decoration-none flex-fill text-dark'>
|
<Link to={`/n/${entry.identifier}`} className='text-decoration-none flex-fill text-dark'>
|
||||||
<div className={'d-flex flex-column justify-content-between'}>
|
<div className={'d-flex flex-column justify-content-between'}>
|
||||||
<Card.Title className='m-0 mt-1dot5'>{entry.title}</Card.Title>
|
<Card.Title className='m-0 mt-1dot5' {...cypressId('history-entry-title')}>
|
||||||
|
{entryTitle}
|
||||||
|
</Card.Title>
|
||||||
<div>
|
<div>
|
||||||
<div className='text-black-50 mt-2'>
|
<div className='text-black-50 mt-2'>
|
||||||
<ForkAwesomeIcon icon='clock-o' /> {DateTime.fromISO(entry.lastVisited).toRelative()}
|
<ForkAwesomeIcon icon='clock-o' /> {DateTime.fromISO(entry.lastVisited).toRelative()}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { EntryMenu } from '../entry-menu/entry-menu'
|
||||||
import type { HistoryEntryProps, HistoryEventHandlers } from '../history-content/history-content'
|
import type { HistoryEntryProps, HistoryEventHandlers } from '../history-content/history-content'
|
||||||
import { PinButton } from '../pin-button/pin-button'
|
import { PinButton } from '../pin-button/pin-button'
|
||||||
import { formatHistoryDate } from '../utils'
|
import { formatHistoryDate } from '../utils'
|
||||||
|
import { useHistoryEntryTitle } from '../use-history-entry-title'
|
||||||
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
|
||||||
export const HistoryTableRow: React.FC<HistoryEntryProps & HistoryEventHandlers> = ({
|
export const HistoryTableRow: React.FC<HistoryEntryProps & HistoryEventHandlers> = ({
|
||||||
entry,
|
entry,
|
||||||
|
@ -18,11 +20,12 @@ export const HistoryTableRow: React.FC<HistoryEntryProps & HistoryEventHandlers>
|
||||||
onRemoveClick,
|
onRemoveClick,
|
||||||
onDeleteClick
|
onDeleteClick
|
||||||
}) => {
|
}) => {
|
||||||
|
const entryTitle = useHistoryEntryTitle(entry)
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Link to={`/n/${entry.identifier}`} className='text-light'>
|
<Link to={`/n/${entry.identifier}`} className='text-light' {...cypressId('history-entry-title')}>
|
||||||
{entry.title}
|
{entryTitle}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
||||||
|
@ -42,7 +45,7 @@ export const HistoryTableRow: React.FC<HistoryEntryProps & HistoryEventHandlers>
|
||||||
/>
|
/>
|
||||||
<EntryMenu
|
<EntryMenu
|
||||||
id={entry.identifier}
|
id={entry.identifier}
|
||||||
title={entry.title}
|
title={entryTitle}
|
||||||
origin={entry.origin}
|
origin={entry.origin}
|
||||||
isDark={true}
|
isDark={true}
|
||||||
onRemove={() => onRemoveClick(entry.identifier)}
|
onRemove={() => onRemoveClick(entry.identifier)}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Pager } from '../../common/pagination/pager'
|
||||||
import type { HistoryEntriesProps, HistoryEventHandlers } from '../history-content/history-content'
|
import type { HistoryEntriesProps, HistoryEventHandlers } from '../history-content/history-content'
|
||||||
import { HistoryTableRow } from './history-table-row'
|
import { HistoryTableRow } from './history-table-row'
|
||||||
import './history-table.scss'
|
import './history-table.scss'
|
||||||
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
|
||||||
export const HistoryTable: React.FC<HistoryEntriesProps & HistoryEventHandlers> = ({
|
export const HistoryTable: React.FC<HistoryEntriesProps & HistoryEventHandlers> = ({
|
||||||
entries,
|
entries,
|
||||||
|
@ -22,7 +23,7 @@ export const HistoryTable: React.FC<HistoryEntriesProps & HistoryEventHandlers>
|
||||||
}) => {
|
}) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<Table striped bordered hover size='sm' variant='dark' className={'history-table'}>
|
<Table striped bordered hover size='sm' variant='dark' className={'history-table'} {...cypressId('history-table')}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { HistoryEntryOrigin } from '../../../redux/history/types'
|
||||||
import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods'
|
import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods'
|
||||||
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
|
||||||
export type HistoryToolbarChange = (newState: HistoryToolbarState) => void
|
export type HistoryToolbarChange = (newState: HistoryToolbarState) => void
|
||||||
|
|
||||||
|
@ -223,7 +224,11 @@ export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange
|
||||||
<ToggleButton className={'btn-light'} value={ViewStateEnum.CARD} title={t('landing.history.toolbar.cards')}>
|
<ToggleButton className={'btn-light'} value={ViewStateEnum.CARD} title={t('landing.history.toolbar.cards')}>
|
||||||
<ForkAwesomeIcon icon={'sticky-note'} className={'fa-fix-line-height'} />
|
<ForkAwesomeIcon icon={'sticky-note'} className={'fa-fix-line-height'} />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton className={'btn-light'} value={ViewStateEnum.TABLE} title={t('landing.history.toolbar.table')}>
|
<ToggleButton
|
||||||
|
{...cypressId('history-mode-table')}
|
||||||
|
className={'btn-light'}
|
||||||
|
value={ViewStateEnum.TABLE}
|
||||||
|
title={t('landing.history.toolbar.table')}>
|
||||||
<ForkAwesomeIcon icon={'table'} className={'fa-fix-line-height'} />
|
<ForkAwesomeIcon icon={'table'} className={'fa-fix-line-height'} />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
|
21
src/components/history-page/use-history-entry-title.ts
Normal file
21
src/components/history-page/use-history-entry-title.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { HistoryEntry } from '../../redux/history/types'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that returns the title of a note in the history if present or the translation for "untitled" otherwise.
|
||||||
|
* @param entry The history entry containing a title property, that might be an empty string.
|
||||||
|
* @return A memoized string containing either the title of the entry or the translated version of "untitled".
|
||||||
|
*/
|
||||||
|
export const useHistoryEntryTitle = (entry: HistoryEntry): string => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return useMemo(() => {
|
||||||
|
return entry.title !== '' ? entry.title : t('editor.untitledNote')
|
||||||
|
}, [t, entry])
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue