Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions demo/src/stories/examples/markup-line-numbers/Editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import {memo, useState} from 'react';

import {
MarkdownEditorView,
NumberInput,
setHighlightedLine,
useMarkdownEditor,
} from '@gravity-ui/markdown-editor';
import type {MarkupLineNumbersConfig} from '@gravity-ui/markdown-editor';
import {Button, Flex} from '@gravity-ui/uikit';

import {PlaygroundLayout} from '../../../components/PlaygroundLayout';

const longMarkup = [
'# Markup Line Numbers Demo',
'',
'This document demonstrates the new markup-mode features:',
'line numbers, line highlighting, and scroll-to-line.',
'',
'## Getting Started',
'',
'The editor below is running in **markup mode** (CodeMirror 6).',
'You can see line numbers in the gutter on the left side.',
'',
'### Line Numbers',
'',
'When `markupConfig.lineNumbers` is enabled, the editor',
'displays line numbers in the left gutter. This is useful',
'for referencing specific lines in documentation or code reviews.',
'',
'### Line Highlighting',
'',
'When `lineHighlight()` extension is passed via `markupConfig.extensions`,',
'clicking on a line number in the gutter will highlight that entire line.',
'This extension automatically includes line numbers as well.',
'',
'You can also programmatically highlight a line using the',
'`setHighlightedLine` StateEffect exported from the package.',
'',
'### Scroll to Line',
'',
'The `markupConfig.lineNumbers.scrollToLine` option allows you to scroll the',
'editor to a specific line (0-based) on mount. This is useful',
'when you want to draw attention to a particular section of',
'a long document.',
'',
'## Example Content',
'',
'Here is some additional content to make the document long',
'enough to demonstrate scrolling behavior.',
'',
'### Lists',
'',
'- Item one',
'- Item two',
'- Item three',
'- Item four',
'- Item five',
'',
'### Code Block',
'',
'```typescript',
"import {lineHighlight, useMarkdownEditor} from '@gravity-ui/markdown-editor';",
'',
'const editor = useMarkdownEditor({',
" initial: {mode: 'markup'},",
' markupConfig: {',
' extensions: [lineHighlight()],',
' },',
'});',
'```',
'',
'### Table',
'',
'| Feature | Option | Default |',
'|---------|--------|---------|',
'| Line numbers | `markupConfig.lineNumbers` | `false` |',
'| Highlight line | `markupConfig.extensions: [lineHighlight()]` | — |',
'| Scroll to line | `markupConfig.lineNumbers.scrollToLine` | `undefined` |',
'',
'### More Text',
'',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
'',
'Duis aute irure dolor in reprehenderit in voluptate velit esse',
'cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat',
'cupidatat non proident, sunt in culpa qui officia deserunt mollit.',
'',
'### Final Section',
'',
'This is the end of the demo document. If `scrollToLine` is set',
'to a value like 20, the editor should scroll to approximately',
'this area of the document on initialization.',
'',
'> **Tip:** Try clicking on line numbers in the gutter to highlight lines!',
].join('\n');

export type MarkupLineNumbersEditorProps = {
lineNumbers?: MarkupLineNumbersConfig;
};

export const MarkupLineNumbersEditor = memo<MarkupLineNumbersEditorProps>(
function MarkupLineNumbersEditor({lineNumbers}) {
const [fromLine, setFromLine] = useState<number | undefined>(
lineNumbers?.initialSelectedLines?.from ?? 0,
);
const [toLine, setToLine] = useState<number | undefined>(
lineNumbers?.initialSelectedLines?.to ?? 0,
);
const [lastClickedLine, setLastClickedLine] = useState<number | null>(null);

const markupLineNumbers: MarkupLineNumbersConfig | undefined = lineNumbers
? {
...lineNumbers,
onLineClick: (line) => setLastClickedLine(line),
}
: undefined;

const editor = useMarkdownEditor(
{
initial: {
mode: 'markup',
markup: longMarkup,
},
markupConfig: {
lineNumbers: markupLineNumbers,
},
},
[],
);

const handleHighlightLine = () => {
if (typeof fromLine !== 'number' || Number.isNaN(fromLine)) return;
if (typeof toLine !== 'number' || Number.isNaN(toLine)) return;

const cm = (editor as any).cm;
if (cm) {
cm.dispatch({effects: setHighlightedLine.of({from: fromLine, to: toLine})});
}
};

return (
<PlaygroundLayout
title="Markup Line Numbers"
editor={editor}
actions={({className}) =>
lineNumbers?.highlightLines ? (
<Flex gap="2" className={className} alignItems="center">
<span>From line:</span>
<NumberInput
size="s"
value={fromLine}
onUpdate={setFromLine}
min={0}
style={{width: '72px'}}
/>
<span>To line:</span>
<NumberInput
size="s"
value={toLine}
onUpdate={setToLine}
min={0}
style={{width: '72px'}}
/>
<Button size="s" onClick={handleHighlightLine}>
Highlight
</Button>
<Button
size="s"
view="outlined"
onClick={() => {
const cm = (editor as any).cm;
if (cm) {
cm.dispatch({effects: setHighlightedLine.of(null)});
}
}}
>
Clear
</Button>
{lastClickedLine !== null && (
<span>Last clicked: line {lastClickedLine + 1}</span>
)}
</Flex>
) : null
}
view={({className}) => (
<MarkdownEditorView
autofocus
stickyToolbar
settingsVisible
editor={editor}
className={className}
/>
)}
/>
);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {Meta, StoryObj} from '@storybook/react';

import {MarkupLineNumbersEditor} from './Editor';

const meta: Meta<typeof MarkupLineNumbersEditor> = {
title: 'Examples / Markup Line Numbers',
component: MarkupLineNumbersEditor,
};

export default meta;

type Story = StoryObj<typeof MarkupLineNumbersEditor>;

export const LineNumbersOnly: Story = {
args: {
lineNumbers: {enabled: true},
},
};
LineNumbersOnly.storyName = 'Line Numbers Only';

export const HighlightLine: Story = {
args: {
lineNumbers: {enabled: true, highlightLines: true},
},
};
HighlightLine.storyName = 'Highlight Line';

export const HighlightMultipleLines: Story = {
args: {
lineNumbers: {
enabled: true,
highlightLines: true,
initialSelectedLines: {from: 5, to: 10},
},
},
};
HighlightMultipleLines.storyName = 'Highlight Multiple Lines';

export const ScrollToLine: Story = {
args: {
lineNumbers: {
enabled: true,
scrollToLine: 20,
initialSelectedLines: {from: 20, to: 20},
highlightLines: true,
},
},
};
ScrollToLine.storyName = 'Scroll to Line';

export const AllFeatures: Story = {
args: {
lineNumbers: {
enabled: true,
highlightLines: true,
initialSelectedLines: {from: 20, to: 25},
scrollToLine: 20,
},
},
};
AllFeatures.storyName = 'All Features';
10 changes: 10 additions & 0 deletions packages/editor/src/bundle/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface EditorInt
readonly mdOptions: Readonly<MarkdownEditorMdOptions>;
readonly directiveSyntax: DirectiveSyntaxContext;
readonly mobile: boolean;
readonly markupConfig: MarkupConfig;

/** @internal used in demo for dev-tools */
readonly _wysiwygView?: PMEditorView;
Expand Down Expand Up @@ -280,6 +281,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
directiveSyntax: this.directiveSyntax,
receiver: this,
searchPanel: this.#markupConfig.searchPanel,
lineNumbers: this.#markupConfig.lineNumbers,
}),
);
}
Expand Down Expand Up @@ -310,6 +312,14 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
return this.#mobile;
}

get initialScrollToLine(): number | undefined {
return this.#markupConfig.lineNumbers?.scrollToLine;
}

get markupConfig(): MarkupConfig {
return this.#markupConfig;
}

constructor(opts: EditorOptions) {
const {logger} = opts;

Expand Down
8 changes: 8 additions & 0 deletions packages/editor/src/bundle/MarkupEditorComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export const MarkupEditorComponent: React.FC<MarkupEditorComponentProps> =
}
}, [editor.markupEditor]);

// scroll to line on mount
useEffect(() => {
const scrollToLine = editor.markupConfig.lineNumbers?.scrollToLine;
if (editor.markupConfig.lineNumbers?.enabled && scrollToLine !== undefined) {
editor.moveCursor({line: scrollToLine});
}
}, []);

return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
Expand Down
14 changes: 13 additions & 1 deletion packages/editor/src/bundle/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type {ReactNode} from 'react';
import type {MarkupString} from '../common';
import type {EscapeConfig, Extension} from '../core';
import type {Logger2} from '../logger';
import type {CreateCodemirrorParams, YfmLangOptions} from '../markup';
import type {CreateCodemirrorParams} from '../markup/codemirror/create';
import type {MarkupLineNumbersConfig} from '../markup/codemirror/line-highlight/types';
import type {YfmLangOptions} from '../markup/codemirror/yfm';
import type {FileUploadHandler} from '../utils';
import type {DirectiveSyntaxContext, DirectiveSyntaxOption} from '../utils/directive';
import type {ParseInsertedUrlAsImage} from '../utils/upload';
Expand Down Expand Up @@ -117,6 +119,7 @@ export type MarkdownEditorExperimentalOptions = {
*/
preserveMarkupFormatting?: boolean;
};
export type {MarkupLineNumbersConfig};

export type MarkdownEditorMarkupConfig = {
/**
Expand Down Expand Up @@ -164,6 +167,15 @@ export type MarkdownEditorMarkupConfig = {
* @default true
*/
searchPanel?: boolean;
/**
* Line numbers configuration for the markup editor.
*
* When `enabled` is true, line numbers gutter is shown.
* When `highlightLines` is true, line highlighting extension is enabled
* (includes clickable line numbers and highlight decoration).
* @default undefined
*/
lineNumbers?: MarkupLineNumbersConfig;
};

// do not export this type
Expand Down
10 changes: 9 additions & 1 deletion packages/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ export * from './view';
export * from './utils';
export * from './bundle';

export {DirectiveSyntaxFacet, ReactRendererFacet, getImageDimensions} from './markup';
export type {LineRange, LineHighlightOptions} from './markup';
export type {MarkupLineNumbersConfig} from './bundle/types';
export {
DirectiveSyntaxFacet,
ReactRendererFacet,
getImageDimensions,
lineHighlight,
setHighlightedLine,
} from './markup';
export * as MarkupCommands from './markup/commands';
export * as MarkupHelpers from './markup/commands/helpers';

Expand Down
Loading
Loading