Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
2.5 kB · 73 lines
TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374/** * Inline emphasis: bold (`**`/`__`), italic (`*`/`_`), strikethrough (`~~`). * * Applies a content class to the span and hides the surrounding markers unless * the cursor is near (same line, within PROXIMITY_CHARS). Both `Emphasis` and * `StrongEmphasis` use `EmphasisMark` for their markers — there is no * `StrongEmphasisMark` in @lezer/markdown; the parent node name disambiguates. * Hidden marker ranges are registered as atomic so cursor motion skips them. */import { type EditorState } from '@codemirror/state';import { Decoration } from '@codemirror/view';import { syntaxTree } from '@codemirror/language';
import { type BuildResult, type Range, DecoCollector, fullRanges, isCursorNearSameLine, decorationExtension,} from './context';import { inlineFormattingHiddenMark } from './hiddenClasses';
const boldMark = Decoration.mark({ class: 'cm-strong' });const italicMark = Decoration.mark({ class: 'cm-em' });const strikethroughMark = Decoration.mark({ class: 'cm-strikethrough' });
/** Lezer container node name → content decoration. */const CONTENT_MARKS: Record<string, Decoration> = { StrongEmphasis: boldMark, Emphasis: italicMark, Strikethrough: strikethroughMark,};const CONTAINER_NAMES = new Set(Object.keys(CONTENT_MARKS));
export function buildInlineFormatDecorations( state: EditorState, ranges: readonly Range[] = fullRanges(state),): BuildResult { const out = new DecoCollector();
for (const { from, to } of ranges) { syntaxTree(state).iterate({ from, to, enter(node) { const contentMark = CONTENT_MARKS[node.name]; if (contentMark) { out.push(node.from, node.to, contentMark); return; // keep descending to reach the markers }
if (node.name === 'EmphasisMark' || node.name === 'StrikethroughMark') { // Widen the proximity window to the whole formatted span when the // parent is a known container; otherwise fall back to the marker. const parent = node.node.parent; const scoped = parent && CONTAINER_NAMES.has(parent.name); const checkFrom = scoped ? parent.from : node.from; const checkTo = scoped ? parent.to : node.to; if (!isCursorNearSameLine(state, checkFrom, checkTo)) { out.push(node.from, node.to, inlineFormattingHiddenMark, true); } } }, }); }
return out.result();}
export const inlineFormatDecoration = decorationExtension( buildInlineFormatDecorations,);