mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-14 02:36:51 +00:00
Merge pull request #12557 from overleaf/ii-history-react-labels-only
[web] History labels only list GitOrigin-RevId: 58b8e5a5af0754e32841223f9c478c25900df526
This commit is contained in:
parent
04c204f989
commit
481cd14cb1
21 changed files with 357 additions and 206 deletions
services/web
frontend
extracted-translations.json
js
features/history
components/change-list
changes.tsxhistory-version.tsxlabel-badges.tsxlabels-list.tsxmetadata-users-list.tsxtag-tooltip.tsxuser-name-with-colored-badge.tsx
context/types
services
utils
shared/components
stylesheets
locales
|
@ -711,6 +711,7 @@
|
|||
"save_x_percent_or_more": "",
|
||||
"saved_bibtex_appended_to_galileo_bib": "",
|
||||
"saved_bibtex_to_new_galileo_bib": "",
|
||||
"saved_by": "",
|
||||
"saving": "",
|
||||
"search": "",
|
||||
"search_bib_files": "",
|
||||
|
|
|
@ -3,21 +3,21 @@ import { getProjectOpDoc } from '../../utils/history-details'
|
|||
import { LoadedUpdate } from '../../services/types/update'
|
||||
|
||||
type ChangesProps = {
|
||||
pathNames: LoadedUpdate['pathnames']
|
||||
pathnames: LoadedUpdate['pathnames']
|
||||
projectOps: LoadedUpdate['project_ops']
|
||||
}
|
||||
|
||||
function Changes({ pathNames, projectOps }: ChangesProps) {
|
||||
function Changes({ pathnames, projectOps }: ChangesProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ol className="history-version-changes">
|
||||
{pathNames.map(pathName => (
|
||||
<li key={pathName}>
|
||||
{pathnames.map(pathname => (
|
||||
<li key={pathname}>
|
||||
<div className="history-version-change-action">
|
||||
{t('file_action_edited')}
|
||||
</div>
|
||||
<div className="history-version-change-doc">{pathName}</div>
|
||||
<div className="history-version-change-doc">{pathname}</div>
|
||||
</li>
|
||||
))}
|
||||
{projectOps.map((op, index) => (
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import LabelBadges from './label-badges'
|
||||
import TagTooltip from './tag-tooltip'
|
||||
import Changes from './changes'
|
||||
import MetadataUsersList from './metadata-users-list'
|
||||
import Origin from './origin'
|
||||
import { useUserContext } from '../../../../shared/context/user-context'
|
||||
import { relativeDate, formatTime } from '../../../utils/format-date'
|
||||
import { orderBy } from 'lodash'
|
||||
import { LoadedUpdate } from '../../services/types/update'
|
||||
|
||||
type HistoryEntryProps = {
|
||||
|
@ -12,6 +13,7 @@ type HistoryEntryProps = {
|
|||
|
||||
function HistoryVersion({ update }: HistoryEntryProps) {
|
||||
const { id: currentUserId } = useUserContext()
|
||||
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -21,26 +23,24 @@ function HistoryVersion({ update }: HistoryEntryProps) {
|
|||
</time>
|
||||
)}
|
||||
<div className="history-version-details">
|
||||
<div>
|
||||
<time className="history-version-metadata-time">
|
||||
{formatTime(update.meta.end_ts, 'Do MMMM, h:mm a')}
|
||||
</time>
|
||||
<LabelBadges
|
||||
labels={update.labels}
|
||||
<time className="history-version-metadata-time">
|
||||
<b>{formatTime(update.meta.end_ts, 'Do MMMM, h:mm a')}</b>
|
||||
</time>
|
||||
{orderedLabels.map(label => (
|
||||
<TagTooltip
|
||||
key={label.id}
|
||||
showTooltip
|
||||
currentUserId={currentUserId}
|
||||
label={label}
|
||||
/>
|
||||
<Changes
|
||||
pathNames={update.pathnames}
|
||||
projectOps={update.project_ops}
|
||||
/>
|
||||
<MetadataUsersList
|
||||
users={update.meta.users}
|
||||
origin={update.meta.origin}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
<Origin origin={update.meta.origin} />
|
||||
</div>
|
||||
))}
|
||||
<Changes pathnames={update.pathnames} projectOps={update.project_ops} />
|
||||
<MetadataUsersList
|
||||
users={update.meta.users}
|
||||
origin={update.meta.origin}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
<Origin origin={update.meta.origin} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
import { Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../shared/components/tooltip'
|
||||
import { useHistoryContext } from '../../context/history-context'
|
||||
import { isPseudoLabel } from '../../utils/label'
|
||||
import { formatDate } from '../../../../utils/dates'
|
||||
import { orderBy } from 'lodash'
|
||||
import { Label, PseudoCurrentStateLabel } from '../../services/types/update'
|
||||
|
||||
type LabelBadgesProps = {
|
||||
showTooltip: boolean
|
||||
currentUserId: string
|
||||
labels: Array<Label | PseudoCurrentStateLabel>
|
||||
}
|
||||
|
||||
function LabelBadges({ labels, currentUserId, showTooltip }: LabelBadgesProps) {
|
||||
const { t } = useTranslation()
|
||||
const { labels: allLabels } = useHistoryContext()
|
||||
const orderedLabels = orderBy(labels, ['created_at'], ['desc'])
|
||||
|
||||
const handleDelete = (e: React.MouseEvent, label: Label) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{orderedLabels.map(label => {
|
||||
const isPseudoCurrentStateLabel = isPseudoLabel(label)
|
||||
const isOwnedByCurrentUser = !isPseudoCurrentStateLabel
|
||||
? label.user_id === currentUserId
|
||||
: null
|
||||
const currentLabelData = allLabels?.find(({ id }) => id === label.id)
|
||||
const labelOwnerName =
|
||||
currentLabelData && !isPseudoLabel(currentLabelData)
|
||||
? currentLabelData.user_display_name
|
||||
: t('anonymous')
|
||||
|
||||
const badgeContent = (
|
||||
<span className="history-version-badge">
|
||||
<Icon type="tag" fw />
|
||||
<span className="history-version-badge-comment">
|
||||
{isPseudoCurrentStateLabel
|
||||
? t('history_label_project_current_state')
|
||||
: label.comment}
|
||||
</span>
|
||||
{isOwnedByCurrentUser && !isPseudoCurrentStateLabel && (
|
||||
<button
|
||||
type="button"
|
||||
className="history-version-label-delete-btn"
|
||||
onClick={e => handleDelete(e, label)}
|
||||
aria-label={t('delete')}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment key={label.id}>
|
||||
{showTooltip && !isPseudoCurrentStateLabel ? (
|
||||
<Tooltip
|
||||
key={label.id}
|
||||
description={
|
||||
<div className="history-version-label-tooltip">
|
||||
<div className="history-version-label-tooltip-row">
|
||||
<b>
|
||||
<Icon type="tag" fw />
|
||||
{label.comment}
|
||||
</b>
|
||||
</div>
|
||||
<div className="history-version-label-tooltip-row">
|
||||
{t('history_label_created_by')} {labelOwnerName}
|
||||
</div>
|
||||
<div className="history-version-label-tooltip-row">
|
||||
<time>{formatDate(label.created_at)}</time>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
id={label.id}
|
||||
overlayProps={{ placement: 'left' }}
|
||||
>
|
||||
{badgeContent}
|
||||
</Tooltip>
|
||||
) : (
|
||||
badgeContent
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabelBadges
|
|
@ -1,5 +1,63 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Fragment } from 'react'
|
||||
import TagTooltip from './tag-tooltip'
|
||||
import UserNameWithColoredBadge from './user-name-with-colored-badge'
|
||||
import { useHistoryContext } from '../../context/history-context'
|
||||
import { useUserContext } from '../../../../shared/context/user-context'
|
||||
import { isPseudoLabel } from '../../utils/label'
|
||||
import { formatTime } from '../../../utils/format-date'
|
||||
import { groupBy, orderBy } from 'lodash'
|
||||
import { LoadedLabel } from '../../services/types/label'
|
||||
|
||||
function LabelsList() {
|
||||
return <div>Labels only</div>
|
||||
const { t } = useTranslation()
|
||||
const { labels } = useHistoryContext()
|
||||
const { id: currentUserId } = useUserContext()
|
||||
|
||||
let versionWithLabels: { version: number; labels: LoadedLabel[] }[] = []
|
||||
if (labels) {
|
||||
const groupedLabelsHash = groupBy(labels, 'version')
|
||||
versionWithLabels = Object.keys(groupedLabelsHash).map(key => ({
|
||||
version: parseInt(key, 10),
|
||||
labels: groupedLabelsHash[key],
|
||||
}))
|
||||
versionWithLabels = orderBy(versionWithLabels, ['version'], ['desc'])
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{versionWithLabels.map(({ version, labels }) => (
|
||||
<div key={version} className="history-version-details">
|
||||
{labels.map(label => (
|
||||
<Fragment key={label.id}>
|
||||
<TagTooltip
|
||||
showTooltip={false}
|
||||
currentUserId={currentUserId}
|
||||
label={label}
|
||||
/>
|
||||
<time className="history-version-metadata-time">
|
||||
{formatTime(label.created_at, 'Do MMMM, h:mm a')}
|
||||
</time>
|
||||
{!isPseudoLabel(label) && (
|
||||
<div className="history-version-saved-by">
|
||||
<span className="history-version-saved-by-label">
|
||||
{t('saved_by')}
|
||||
</span>
|
||||
<UserNameWithColoredBadge
|
||||
user={{
|
||||
id: label.user_id,
|
||||
displayName: label.user_display_name,
|
||||
}}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabelsList
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { getUserColor, formatUserName } from '../../utils/history-details'
|
||||
import { getUserColor } from '../../utils/history-details'
|
||||
import { LoadedUpdate } from '../../services/types/update'
|
||||
import UserNameWithColoredBadge from './user-name-with-colored-badge'
|
||||
|
||||
type MetadataUsersListProps = {
|
||||
currentUserId: string
|
||||
|
@ -15,26 +16,11 @@ function MetadataUsersList({
|
|||
|
||||
return (
|
||||
<ol className="history-version-metadata-users">
|
||||
{users.map((user, index) => {
|
||||
let userName: string
|
||||
if (!user) {
|
||||
userName = t('anonymous')
|
||||
} else if (user?.id === currentUserId) {
|
||||
userName = t('you')
|
||||
} else {
|
||||
userName = formatUserName(user)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
<span
|
||||
className="history-version-user-badge-color"
|
||||
style={{ backgroundColor: getUserColor(user) }}
|
||||
/>
|
||||
{userName}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{users.map((user, index) => (
|
||||
<li key={index}>
|
||||
<UserNameWithColoredBadge user={user} currentUserId={currentUserId} />
|
||||
</li>
|
||||
))}
|
||||
{!users.length && (
|
||||
<li>
|
||||
<span
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../../shared/components/icon'
|
||||
import Tooltip from '../../../../shared/components/tooltip'
|
||||
import Badge from '../../../../shared/components/badge'
|
||||
import { useHistoryContext } from '../../context/history-context'
|
||||
import { isPseudoLabel } from '../../utils/label'
|
||||
import { formatDate } from '../../../../utils/dates'
|
||||
import { LoadedLabel } from '../../services/types/label'
|
||||
|
||||
type TagProps = {
|
||||
label: LoadedLabel
|
||||
currentUserId: string
|
||||
}
|
||||
|
||||
function Tag({ label, currentUserId, ...props }: TagProps) {
|
||||
const { t } = useTranslation()
|
||||
const isPseudoCurrentStateLabel = isPseudoLabel(label)
|
||||
const isOwnedByCurrentUser = !isPseudoCurrentStateLabel
|
||||
? label.user_id === currentUserId
|
||||
: null
|
||||
|
||||
const handleDelete = (e: React.MouseEvent, label: LoadedLabel) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge
|
||||
prepend={<Icon type="tag" fw />}
|
||||
onClose={e => handleDelete(e, label)}
|
||||
showCloseButton={Boolean(
|
||||
isOwnedByCurrentUser && !isPseudoCurrentStateLabel
|
||||
)}
|
||||
closeBtnProps={{ 'aria-label': t('delete') }}
|
||||
className="history-version-badge"
|
||||
{...props}
|
||||
>
|
||||
{isPseudoCurrentStateLabel
|
||||
? t('history_label_project_current_state')
|
||||
: label.comment}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
type LabelBadgesProps = {
|
||||
showTooltip: boolean
|
||||
currentUserId: string
|
||||
label: LoadedLabel
|
||||
}
|
||||
|
||||
function TagTooltip({ label, currentUserId, showTooltip }: LabelBadgesProps) {
|
||||
const { t } = useTranslation()
|
||||
const { labels: allLabels } = useHistoryContext()
|
||||
|
||||
const isPseudoCurrentStateLabel = isPseudoLabel(label)
|
||||
const currentLabelData = allLabels?.find(({ id }) => id === label.id)
|
||||
const labelOwnerName =
|
||||
currentLabelData && !isPseudoLabel(currentLabelData)
|
||||
? currentLabelData.user_display_name
|
||||
: t('anonymous')
|
||||
|
||||
return showTooltip && !isPseudoCurrentStateLabel ? (
|
||||
<Tooltip
|
||||
key={label.id}
|
||||
description={
|
||||
<div className="history-version-label-tooltip">
|
||||
<div className="history-version-label-tooltip-row">
|
||||
<b>
|
||||
<Icon type="tag" fw />
|
||||
{label.comment}
|
||||
</b>
|
||||
</div>
|
||||
<div className="history-version-label-tooltip-row">
|
||||
{t('history_label_created_by')} {labelOwnerName}
|
||||
</div>
|
||||
<div className="history-version-label-tooltip-row">
|
||||
<time>{formatDate(label.created_at)}</time>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
id={label.id}
|
||||
overlayProps={{ placement: 'left' }}
|
||||
>
|
||||
<Tag label={label} currentUserId={currentUserId} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tag label={label} currentUserId={currentUserId} />
|
||||
)
|
||||
}
|
||||
|
||||
export default TagTooltip
|
|
@ -0,0 +1,39 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { formatUserName, getUserColor } from '../../utils/history-details'
|
||||
import { User } from '../../services/types/shared'
|
||||
import { Nullable } from '../../../../../../types/utils'
|
||||
|
||||
type UserNameWithColoredBadgeProps = {
|
||||
currentUserId: string
|
||||
user: Nullable<User | { id: string; displayName: string }>
|
||||
}
|
||||
|
||||
function UserNameWithColoredBadge({
|
||||
user,
|
||||
currentUserId,
|
||||
}: UserNameWithColoredBadgeProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
let userName: string
|
||||
if (!user) {
|
||||
userName = t('anonymous')
|
||||
} else if (user.id === currentUserId) {
|
||||
userName = t('you')
|
||||
} else if ('displayName' in user) {
|
||||
userName = user.displayName
|
||||
} else {
|
||||
userName = formatUserName(user)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className="history-version-user-badge-color"
|
||||
style={{ backgroundColor: getUserColor(user) }}
|
||||
/>
|
||||
{userName}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserNameWithColoredBadge
|
|
@ -1,10 +1,6 @@
|
|||
import { Nullable } from '../../../../../../types/utils'
|
||||
import {
|
||||
Label,
|
||||
LoadedUpdate,
|
||||
PseudoCurrentStateLabel,
|
||||
UpdateSelection,
|
||||
} from '../../services/types/update'
|
||||
import { LoadedUpdate, UpdateSelection } from '../../services/types/update'
|
||||
import { LoadedLabel } from '../../services/types/label'
|
||||
import { Selection } from '../../../../../../types/history/selection'
|
||||
import { FileSelection } from '../../services/types/file'
|
||||
import { ViewMode } from '../../services/types/view-mode'
|
||||
|
@ -19,7 +15,7 @@ export type HistoryContextValue = {
|
|||
selection: Selection
|
||||
isLoading: boolean
|
||||
error: Nullable<unknown>
|
||||
labels: Nullable<Array<Label | PseudoCurrentStateLabel>>
|
||||
labels: Nullable<LoadedLabel[]>
|
||||
loadingFileTree: boolean
|
||||
projectId: string
|
||||
fileSelection: FileSelection | null
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
import { FileDiff } from './types/file'
|
||||
import { Label, Update } from './types/update'
|
||||
import { Update } from './types/update'
|
||||
import { Label } from './types/label'
|
||||
import { DocDiffResponse } from './types/doc'
|
||||
|
||||
const BATCH_SIZE = 10
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Nullable } from '../../../../../../types/utils'
|
||||
|
||||
interface UpdateLabel {
|
||||
id: string
|
||||
comment: string
|
||||
version: number
|
||||
user_id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Label extends UpdateLabel {
|
||||
user_display_name: string
|
||||
}
|
||||
|
||||
export interface PseudoCurrentStateLabel {
|
||||
id: '1'
|
||||
isPseudoCurrentStateLabel: true
|
||||
version: Nullable<number>
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type LoadedLabel = Label | PseudoCurrentStateLabel
|
|
@ -1,25 +1,7 @@
|
|||
import { Meta, User } from './shared'
|
||||
import { Label } from './label'
|
||||
import { Nullable } from '../../../../../../types/utils'
|
||||
|
||||
interface UpdateLabel {
|
||||
id: string
|
||||
comment: string
|
||||
version: number
|
||||
user_id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Label extends UpdateLabel {
|
||||
user_display_name: string
|
||||
}
|
||||
|
||||
export interface PseudoCurrentStateLabel {
|
||||
id: '1'
|
||||
isPseudoCurrentStateLabel: true
|
||||
version: Nullable<number>
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface ProjectOp {
|
||||
add?: { pathname: string }
|
||||
rename?: { pathname: string; newPathname: string }
|
||||
|
@ -36,7 +18,7 @@ export interface Update {
|
|||
project_ops: ProjectOp[]
|
||||
}
|
||||
|
||||
interface LoadedUpdateMetaUser extends User {
|
||||
export interface LoadedUpdateMetaUser extends User {
|
||||
hue?: number
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,8 @@ import { Nullable } from '../../../../../types/utils'
|
|||
import { User } from '../services/types/shared'
|
||||
import { ProjectOp } from '../services/types/update'
|
||||
|
||||
export const getUserColor = (
|
||||
user?: Nullable<{ id: string; _id?: never } | { id?: never; _id: string }>
|
||||
) => {
|
||||
const curUserId = user?.id || user?._id
|
||||
const hue = ColorManager.getHueForUserId(curUserId) || 100
|
||||
export const getUserColor = (user?: Nullable<{ id: string }>) => {
|
||||
const hue = ColorManager.getHueForUserId(user?.id) || 100
|
||||
|
||||
return `hsl(${hue}, 70%, 50%)`
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { orderBy } from 'lodash'
|
||||
import { Label, PseudoCurrentStateLabel } from '../services/types/update'
|
||||
import {
|
||||
LoadedLabel,
|
||||
Label,
|
||||
PseudoCurrentStateLabel,
|
||||
} from '../services/types/label'
|
||||
import { Nullable } from '../../../../../types/utils'
|
||||
|
||||
export const isPseudoLabel = (
|
||||
label: Label | PseudoCurrentStateLabel
|
||||
label: LoadedLabel
|
||||
): label is PseudoCurrentStateLabel => {
|
||||
return (label as PseudoCurrentStateLabel).isPseudoCurrentStateLabel === true
|
||||
}
|
||||
|
||||
const sortLabelsByVersionAndDate = (
|
||||
labels: Array<Label | PseudoCurrentStateLabel>
|
||||
) => {
|
||||
const sortLabelsByVersionAndDate = (labels: LoadedLabel[]) => {
|
||||
return orderBy(
|
||||
labels,
|
||||
['isPseudoCurrentStateLabel', 'version', 'created_at'],
|
||||
|
@ -18,9 +20,7 @@ const sortLabelsByVersionAndDate = (
|
|||
)
|
||||
}
|
||||
|
||||
const deletePseudoCurrentStateLabelIfExistent = (
|
||||
labels: Array<Label | PseudoCurrentStateLabel>
|
||||
) => {
|
||||
const deletePseudoCurrentStateLabelIfExistent = (labels: LoadedLabel[]) => {
|
||||
if (labels.length && isPseudoLabel(labels[0])) {
|
||||
const [, ...rest] = labels
|
||||
return rest
|
||||
|
@ -29,7 +29,7 @@ const deletePseudoCurrentStateLabelIfExistent = (
|
|||
}
|
||||
|
||||
const addPseudoCurrentStateLabelIfNeeded = (
|
||||
labels: Array<Label | PseudoCurrentStateLabel>,
|
||||
labels: LoadedLabel[],
|
||||
mostRecentVersion: Nullable<number>
|
||||
) => {
|
||||
if (!labels.length || labels[0].version !== mostRecentVersion) {
|
||||
|
|
43
services/web/frontend/js/shared/components/badge.tsx
Normal file
43
services/web/frontend/js/shared/components/badge.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import classnames from 'classnames'
|
||||
import { MergeAndOverride } from '../../../../types/utils'
|
||||
|
||||
type BadgeProps = MergeAndOverride<
|
||||
React.ComponentProps<'span'>,
|
||||
{
|
||||
prepend?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
showCloseButton?: boolean
|
||||
onClose?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
closeBtnProps?: React.ComponentProps<'button'>
|
||||
}
|
||||
>
|
||||
|
||||
function Badge({
|
||||
prepend,
|
||||
children,
|
||||
className,
|
||||
showCloseButton = false,
|
||||
onClose,
|
||||
closeBtnProps,
|
||||
...rest
|
||||
}: BadgeProps) {
|
||||
return (
|
||||
<span className={classnames('badge-new', className)} {...rest}>
|
||||
{prepend}
|
||||
<span className="badge-new-comment">{children}</span>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
type="button"
|
||||
className="badge-new-close"
|
||||
onClick={onClose}
|
||||
{...closeBtnProps}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default Badge
|
|
@ -25,6 +25,7 @@
|
|||
// Components
|
||||
@import 'components/tables.less';
|
||||
@import 'components/forms.less';
|
||||
@import 'components/badge.less';
|
||||
@import 'components/buttons.less';
|
||||
@import 'components/card.less';
|
||||
//@import "components/code.less";
|
||||
|
|
|
@ -80,8 +80,8 @@ history-root {
|
|||
}
|
||||
|
||||
.history-version-details {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
@ -92,8 +92,11 @@ history-root {
|
|||
.history-version-metadata-time {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
color: @neutral-90;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.history-version-metadata-users,
|
||||
|
@ -131,7 +134,8 @@ history-root {
|
|||
.history-version-day,
|
||||
.history-version-change-action,
|
||||
.history-version-metadata-users,
|
||||
.history-version-origin {
|
||||
.history-version-origin,
|
||||
.history-version-saved-by {
|
||||
color: @neutral-70;
|
||||
}
|
||||
|
||||
|
@ -145,28 +149,20 @@ history-root {
|
|||
}
|
||||
|
||||
.history-version-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 24px;
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 4px;
|
||||
margin-right: 10px;
|
||||
padding: 4px;
|
||||
white-space: nowrap;
|
||||
color: @ol-blue-gray-6;
|
||||
background-color: @neutral-20;
|
||||
border-radius: 4px;
|
||||
|
||||
&-comment {
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.history-version-label-delete-btn {
|
||||
.reset-button;
|
||||
marigin-left: 8px;
|
||||
padding: 4px;
|
||||
font-size: 24px;
|
||||
.history-version-saved-by {
|
||||
margin-bottom: 4px;
|
||||
|
||||
&-label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.history-loading-panel {
|
||||
|
|
32
services/web/frontend/stylesheets/components/badge.less
Normal file
32
services/web/frontend/stylesheets/components/badge.less
Normal file
|
@ -0,0 +1,32 @@
|
|||
.badge-new {
|
||||
@padding: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 24px;
|
||||
padding: @padding;
|
||||
white-space: nowrap;
|
||||
color: @ol-blue-gray-6;
|
||||
background-color: @neutral-20;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: @neutral-30;
|
||||
}
|
||||
|
||||
&-comment {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
&-close {
|
||||
.reset-button;
|
||||
width: 24px;
|
||||
margin-left: 4px;
|
||||
margin-right: -@padding;
|
||||
font-size: 24px;
|
||||
|
||||
&:hover {
|
||||
background-color: @neutral-40;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
@gray-lightest: #f0f0f0;
|
||||
@white: #ffffff;
|
||||
@neutral-20: #e7e9ee;
|
||||
@neutral-30: #d0d5dd;
|
||||
@neutral-90: #1b222c;
|
||||
@neutral-40: #afb5c0;
|
||||
@neutral-10: #f4f5f6;
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
// Components
|
||||
@import 'components/tables.less';
|
||||
@import 'components/forms.less';
|
||||
@import 'components/badge.less';
|
||||
@import 'components/buttons.less';
|
||||
@import 'components/card.less';
|
||||
@import 'components/component-animations.less';
|
||||
|
|
|
@ -1285,6 +1285,7 @@
|
|||
"save_x_percent_or_more": "Save __percent__% or more",
|
||||
"saved_bibtex_appended_to_galileo_bib": "The <strong>__citeKey__</strong> cite key has been added to the <strong>__galileoBib__</strong> file in your project.",
|
||||
"saved_bibtex_to_new_galileo_bib": "The <strong>__citeKey__</strong> cite key has been copied into a new <strong>__galileoBib__</strong> file in your project. Include this file in your project using the appropriate method for your citation package.",
|
||||
"saved_by": "Saved by",
|
||||
"saving": "Saving",
|
||||
"saving_20_percent": "Saving 20%!",
|
||||
"saving_notification_with_seconds": "Saving __docname__... (__seconds__ seconds of unsaved changes)",
|
||||
|
|
Loading…
Add table
Reference in a new issue