feat: editor language menu, title field, header actions, publish dialogs, read-only rendering and draft recovery
This commit is contained in:
1 parent
a671b81f80
commit
339e646eb8
85 files changed
+6369
-624
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { t } from '@/i18n'
|
||||
import type { ArticleCapability, CoverageEntry } from '@/lib/articleTypes'
|
||||
import { holds } from '@/lib/editorCapabilities'
|
||||
import { unpublishLabel } from '@/lib/editorLabels'
|
||||
|
||||
export interface OverflowItem {
|
||||
key: 'unpublish' | 'editSlug' | 'move' | 'deleteVersion' | 'deleteArticle'
|
||||
label: string
|
||||
icon: string
|
||||
danger: boolean
|
||||
disabled: boolean
|
||||
reason: string
|
||||
onSelect: () => void
|
||||
}
|
||||
|
||||
export interface OverflowContext {
|
||||
capabilities: ArticleCapability[]
|
||||
siblings: CoverageEntry[]
|
||||
language: string | null
|
||||
unpublishInMenu: boolean
|
||||
handlers: Partial<Record<OverflowItem['key'], () => void>>
|
||||
}
|
||||
|
||||
function ignore() {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function unavailable(key: OverflowItem['key'], context: OverflowContext): Partial<OverflowItem> {
|
||||
return context.handlers[key] ? {} : { disabled: true, reason: t('Not available yet.') }
|
||||
}
|
||||
|
||||
function item(
|
||||
key: OverflowItem['key'],
|
||||
label: string,
|
||||
icon: string,
|
||||
context: OverflowContext,
|
||||
extra: Partial<OverflowItem> = {},
|
||||
): OverflowItem {
|
||||
return {
|
||||
key,
|
||||
label,
|
||||
icon,
|
||||
danger: false,
|
||||
disabled: false,
|
||||
reason: '',
|
||||
onSelect: context.handlers[key] ?? ignore,
|
||||
...unavailable(key, context),
|
||||
...extra,
|
||||
}
|
||||
}
|
||||
|
||||
function deleteVersionLabel(language: string | null) {
|
||||
return language ? t('Delete {0} version', [language]) : t('Delete article')
|
||||
}
|
||||
|
||||
function blocksWholeArticle(context: OverflowContext) {
|
||||
return context.siblings.some((sibling) => !holds(sibling.capabilities, 'delete'))
|
||||
}
|
||||
|
||||
function blockedExtra(blocked: boolean): Partial<OverflowItem> {
|
||||
return blocked ? { disabled: true, reason: t("You can't delete this article.") } : {}
|
||||
}
|
||||
|
||||
function editItems(context: OverflowContext): OverflowItem[] {
|
||||
const blocked = blocksWholeArticle(context)
|
||||
return [
|
||||
item('editSlug', t('Edit slug'), 'i-lucide-link', context),
|
||||
item(
|
||||
'move',
|
||||
t('Move article'),
|
||||
'i-lucide-arrow-right-left',
|
||||
context,
|
||||
blockedExtra(blocked),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function deleteItems(context: OverflowContext): OverflowItem[] {
|
||||
const versionItem = item(
|
||||
'deleteVersion',
|
||||
deleteVersionLabel(context.language),
|
||||
'i-lucide-trash-2',
|
||||
context,
|
||||
{ danger: true },
|
||||
)
|
||||
if (!context.language) {
|
||||
return [versionItem]
|
||||
}
|
||||
const blocked = blocksWholeArticle(context)
|
||||
return [
|
||||
versionItem,
|
||||
item('deleteArticle', t('Delete article'), 'i-lucide-trash-2', context, {
|
||||
danger: !blocked,
|
||||
...blockedExtra(blocked),
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
export function overflowItems(context: OverflowContext): OverflowItem[] {
|
||||
const items: OverflowItem[] = []
|
||||
if (context.unpublishInMenu) {
|
||||
items.push(item('unpublish', unpublishLabel(context.language), 'i-lucide-eye-off', context))
|
||||
}
|
||||
if (holds(context.capabilities, 'edit')) {
|
||||
items.push(...editItems(context))
|
||||
}
|
||||
if (holds(context.capabilities, 'delete')) {
|
||||
items.push(...deleteItems(context))
|
||||
}
|
||||
return items
|
||||
}
|
||||
Reference in new issue
Block a user