decap-cms-core
Version:
Decap CMS core application, see decap-cms package for the main distribution.
222 lines (192 loc) • 5.79 kB
JavaScript
import PropTypes from 'prop-types';
import { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from '@emotion/styled';
import { List } from 'immutable';
import { colors, Icon } from 'decap-cms-ui-default';
import NotesList from './NotesList';
import AddNoteForm from './AddNoteForm';
const NotesContainer = styled.div`
height: 100%;
display: flex;
flex-direction: column;
background-color: ${colors.background};
border-left: 1px solid ${colors.textFieldBorder};
`;
const NotesHeader = styled.div`
padding: 16px 24px;
border-bottom: 1px solid ${colors.textFieldBorder};
background-color: ${colors.inputBackground};
display: flex;
align-items: center;
min-height: 60px;
`;
const NotesTitleGroup = styled.div`
display: flex;
align-items: center;
gap: 8px;
`;
const NotesTitle = styled.h3`
margin: 0;
font-size: 16px;
font-weight: 600;
color: ${colors.text};
`;
const NotesCount = styled.span`
background-color: ${colors.controlLabel};
color: white;
border-radius: 12px;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
`;
const SourceLink = styled.a`
color: ${colors.controlLabel};
text-decoration: none;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
&:hover {
color: ${colors.text};
text-decoration: underline;
}
`;
const SourceIcon = styled(Icon)`
width: 16px;
height: 16px;
color: currentColor;
`;
const NotesContent = styled.div`
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
`;
const EmptyState = styled.div`
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: ${colors.controlLabel};
text-align: center;
`;
const EmptyStateIcon = styled.div`
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
`;
const EmptyStateText = styled.p`
font-size: 14px;
margin: 0;
line-height: 1.4;
`;
class EditorNotesPane extends Component {
static propTypes = {
notes: ImmutablePropTypes.list,
onChange: PropTypes.func.isRequired,
entry: ImmutablePropTypes.map.isRequired,
collection: ImmutablePropTypes.map.isRequired,
user: PropTypes.object,
t: PropTypes.func,
sourceUrl: PropTypes.string, // Link to the GitHub issue or source
};
static defaultProps = {
notes: List(),
t: key => key, // Fallback translation function
};
handleAddNote = content => {
const { onChange, user } = this.props;
const newNote = {
content: content.trim(),
author: user?.login || user?.name || 'Anonymous',
resolved: false,
};
onChange('ADD_NOTE', newNote);
};
handleUpdateNote = (noteId, updates) => {
const { onChange } = this.props;
onChange('UPDATE_NOTE', { id: noteId, updates });
};
handleDeleteNote = noteId => {
const { onChange } = this.props;
onChange('DELETE_NOTE', { id: noteId });
};
handleToggleNoteResolution = noteId => {
const { notes } = this.props;
const notesList = notes && notes.size !== undefined ? notes : List(notes || []);
const note = notesList.find(n => n.get('id') === noteId);
const currentResolved = note ? note.get('resolved') : false;
this.handleUpdateNote(noteId, { resolved: !currentResolved });
};
// Helper method to get the appropriate link text and icon type based on the source
getSourceInfo = url => {
// Check if URL is from GitHub
if (url.includes('github.com')) {
return {
text: 'View in GitHub',
iconType: 'github', // Using GitHub icon type
};
}
// TODO: Add support for other Git providers
// Example for future contributors:
// if (url.includes('gitlab.com')) {
// return { text: 'View in GitLab', iconType: 'gitlab' };
// }
// if (url.includes('bitbucket.org')) {
// return { text: 'View in Bitbucket', iconType: 'bitbucket' };
// }
// Default fallback
return {
text: 'View source',
iconType: 'link',
};
};
render() {
const { notes, t } = this.props;
const notesList = notes && notes.size !== undefined ? notes : List(notes || []);
const notesCount = notesList.size;
const unresolvedCount = notesList.filter(note => !note.get('resolved')).size;
const sourceUrl = notesCount > 0 ? notesList.first()?.get('issueUrl') : null;
const sourceInfo = sourceUrl ? this.getSourceInfo(sourceUrl) : null;
return (
<NotesContainer>
<NotesHeader>
<NotesTitleGroup>
<NotesTitle>{t('editor.editorNotesPane.title')}</NotesTitle>
{notesCount > 0 && (
<NotesCount>{unresolvedCount > 0 ? unresolvedCount : notesCount}</NotesCount>
)}
{sourceInfo && (
<SourceLink href={sourceUrl} target="_blank" rel="noopener noreferrer">
<span>{sourceInfo.text}</span>
<SourceIcon type={sourceInfo.iconType} />
</SourceLink>
)}
</NotesTitleGroup>
</NotesHeader>
<NotesContent>
{notesCount === 0 ? (
<EmptyState>
<EmptyStateIcon>📝</EmptyStateIcon>
<EmptyStateText>{t('editor.editorNotesPane.emptyState')}</EmptyStateText>
</EmptyState>
) : (
<NotesList
notes={notesList}
onUpdate={this.handleUpdateNote}
onDelete={this.handleDeleteNote}
onToggleResolution={this.handleToggleNoteResolution}
user={this.props.user}
t={t}
/>
)}
<AddNoteForm onAdd={this.handleAddNote} t={t} />
</NotesContent>
</NotesContainer>
);
}
}
export default EditorNotesPane;