import type { Attrs, Node } from '../model'; import { blockById, isCollapsed, marksAt, nodeInline, orderedSelection, rangeHasMarkType } from '../model'; import type { WritekitState } from '../state'; /** Block id the selection's focus is in (or the first node-selected block). */ export function selectionBlockId(state: WritekitState): string | undefined { const sel = state.selection; return sel.kind === 'text' ? sel.focus.blockId : sel.ids[0]; } /** The block the selection currently focuses, or `null`. */ export function focusBlock(state: WritekitState): Node | null { const id = selectionBlockId(state); return id ? blockById(state.doc, id) : null; } /** Whether a block type holds inline (text) content. */ export function isTextBlockType(state: WritekitState, type: string): boolean { return state.schema.nodeSpec(type)?.content.kind === 'text'; } /** * Whether a mark is active for the current selection — used by `toggleMark` and * by toolbars (call a command without `dispatch` for the same answer). */ export function isMarkActive(state: WritekitState, type: string): boolean { const sel = state.selection; if (sel.kind !== 'text') return false; if (isCollapsed(sel)) { if (state.storedMarks) return state.storedMarks.some(mark => mark.type === type); const block = blockById(state.doc, sel.focus.blockId); return block ? marksAt(nodeInline(block), sel.focus.offset).some(mark => mark.type === type) : false; } if (sel.anchor.blockId !== sel.focus.blockId) return false; const { from, to } = orderedSelection(sel, state.doc); const block = blockById(state.doc, sel.focus.blockId); return block ? rangeHasMarkType(nodeInline(block), from.offset, to.offset, type) : false; } /** Whether the focused block matches a type (and optionally a subset of attrs). */ export function isBlockActive(state: WritekitState, type: string, attrs?: Attrs): boolean { const block = focusBlock(state); if (!block || block.type !== type) return false; if (!attrs) return true; return Object.keys(attrs).every(key => block.attrs[key] === attrs[key]); }