admin-vben/packages/effects/plugins/src/tiptap/use-tiptap-toolbar.ts

177 lines
4.4 KiB
TypeScript

import type { Editor } from '@tiptap/vue-3';
import type { ShallowRef } from 'vue';
import type { ToolbarAction, ToolbarMenuItem } from './types';
import { cn } from '@vben-core/shared/utils';
interface UseTiptapToolbarOptions {
editable: () => boolean;
editor: Readonly<ShallowRef<Editor | undefined>>;
}
export function useTiptapToolbar(options: UseTiptapToolbarOptions) {
const getEditor = () => options.editor.value;
function getActionIndicatorColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.indicatorColor) {
return undefined;
}
return action.indicatorColor(currentEditor);
}
function getPaletteCurrentColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette?.currentColor) {
return undefined;
}
return action.palette.currentColor(currentEditor);
}
function canRunAction(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return false;
}
return action.can ? action.can(currentEditor) : true;
}
function canRunMenuItem(item: ToolbarMenuItem) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return false;
}
return item.can ? item.can(currentEditor) : true;
}
function isActionActive(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor) {
return false;
}
if (action.isActive) {
return action.isActive(currentEditor);
}
if (!action.active) {
return false;
}
return currentEditor.isActive(action.active.name, action.active.attrs);
}
function isMenuItemActive(item: ToolbarMenuItem, currentEditor?: Editor) {
const targetEditor = currentEditor ?? getEditor();
if (!targetEditor || !item.isActive) {
return false;
}
return item.isActive(targetEditor);
}
function runAction(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return;
}
if (action.menu || action.palette) {
return;
}
action.action(currentEditor);
}
function runMenuItem(item: ToolbarMenuItem) {
const currentEditor = getEditor();
if (!currentEditor || !options.editable()) {
return;
}
item.action(currentEditor);
}
function applyPaletteColor(action: ToolbarAction, color: string) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette) {
return;
}
action.palette.apply(currentEditor, color);
}
function clearPaletteColor(action: ToolbarAction) {
const currentEditor = getEditor();
if (!currentEditor || !action.palette?.clear) {
return;
}
action.palette.clear(currentEditor);
}
function getToolbarButtonClass(action: ToolbarAction) {
return cn(
'text-muted-foreground relative rounded-[10px] border border-transparent bg-transparent shadow-none',
'transition-[transform,color,background-color,border-color,box-shadow] duration-200 ease-out',
'enabled:hover:border-border enabled:hover:-translate-y-px disabled:opacity-45',
'enabled:hover:bg-accent enabled:hover:text-foreground',
isActionActive(action) &&
'bg-accent border-primary/30 shadow-primary text-primary',
);
}
function getPaletteSwatchClass(action: ToolbarAction, color: string) {
return cn(
'border-border inline-flex size-8 items-center justify-center rounded-full border',
'shadow-accent',
'transition-[transform,box-shadow,border-color] duration-200 ease-out',
'hover:-translate-y-px hover:scale-[1.04]',
getPaletteCurrentColor(action) === color &&
'border-primary shadow-primary',
);
}
function getMenuItemClass(item: ToolbarMenuItem) {
return cn(
'flex items-center gap-2 rounded-lg p-2 text-left text-sm transition-colors',
'disabled:cursor-not-allowed disabled:opacity-45',
isMenuItemActive(item)
? 'bg-accent text-foreground'
: 'hover:bg-accent hover:text-foreground text-muted-foreground',
);
}
return {
applyPaletteColor,
canRunAction,
canRunMenuItem,
clearPaletteColor,
getActionIndicatorColor,
getMenuItemClass,
getPaletteCurrentColor,
getPaletteSwatchClass,
getToolbarButtonClass,
isActionActive,
isMenuItemActive,
runAction,
runMenuItem,
};
}