Skip to content

Commit d7ac2aa

Browse files
author
saxumcordis
committed
feat: add replace to markup search
1 parent 3fa02a9 commit d7ac2aa

11 files changed

Lines changed: 473 additions & 2 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import {memo, useState} from 'react';
2+
3+
import {
4+
MarkdownEditorView,
5+
NumberInput,
6+
setHighlightedLine,
7+
useMarkdownEditor,
8+
} from '@gravity-ui/markdown-editor';
9+
import type {MarkupLineNumbersConfig} from '@gravity-ui/markdown-editor';
10+
import {Button, Flex} from '@gravity-ui/uikit';
11+
12+
import {PlaygroundLayout} from '../../../components/PlaygroundLayout';
13+
14+
const longMarkup = [
15+
'# Markup Line Numbers Demo',
16+
'',
17+
'This document demonstrates the new markup-mode features:',
18+
'line numbers, line highlighting, and scroll-to-line.',
19+
'',
20+
'## Getting Started',
21+
'',
22+
'The editor below is running in **markup mode** (CodeMirror 6).',
23+
'You can see line numbers in the gutter on the left side.',
24+
'',
25+
'### Line Numbers',
26+
'',
27+
'When `markupConfig.lineNumbers` is enabled, the editor',
28+
'displays line numbers in the left gutter. This is useful',
29+
'for referencing specific lines in documentation or code reviews.',
30+
'',
31+
'### Line Highlighting',
32+
'',
33+
'When `lineHighlight()` extension is passed via `markupConfig.extensions`,',
34+
'clicking on a line number in the gutter will highlight that entire line.',
35+
'This extension automatically includes line numbers as well.',
36+
'',
37+
'You can also programmatically highlight a line using the',
38+
'`setHighlightedLine` StateEffect exported from the package.',
39+
'',
40+
'### Scroll to Line',
41+
'',
42+
'The `markupConfig.lineNumbers.scrollToLine` option allows you to scroll the',
43+
'editor to a specific line (0-based) on mount. This is useful',
44+
'when you want to draw attention to a particular section of',
45+
'a long document.',
46+
'',
47+
'## Example Content',
48+
'',
49+
'Here is some additional content to make the document long',
50+
'enough to demonstrate scrolling behavior.',
51+
'',
52+
'### Lists',
53+
'',
54+
'- Item one',
55+
'- Item two',
56+
'- Item three',
57+
'- Item four',
58+
'- Item five',
59+
'',
60+
'### Code Block',
61+
'',
62+
'```typescript',
63+
"import {lineHighlight, useMarkdownEditor} from '@gravity-ui/markdown-editor';",
64+
'',
65+
'const editor = useMarkdownEditor({',
66+
" initial: {mode: 'markup'},",
67+
' markupConfig: {',
68+
' extensions: [lineHighlight()],',
69+
' },',
70+
'});',
71+
'```',
72+
'',
73+
'### Table',
74+
'',
75+
'| Feature | Option | Default |',
76+
'|---------|--------|---------|',
77+
'| Line numbers | `markupConfig.lineNumbers` | `false` |',
78+
'| Highlight line | `markupConfig.extensions: [lineHighlight()]` | — |',
79+
'| Scroll to line | `markupConfig.lineNumbers.scrollToLine` | `undefined` |',
80+
'',
81+
'### More Text',
82+
'',
83+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
84+
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
85+
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
86+
'',
87+
'Duis aute irure dolor in reprehenderit in voluptate velit esse',
88+
'cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat',
89+
'cupidatat non proident, sunt in culpa qui officia deserunt mollit.',
90+
'',
91+
'### Final Section',
92+
'',
93+
'This is the end of the demo document. If `scrollToLine` is set',
94+
'to a value like 20, the editor should scroll to approximately',
95+
'this area of the document on initialization.',
96+
'',
97+
'> **Tip:** Try clicking on line numbers in the gutter to highlight lines!',
98+
].join('\n');
99+
100+
export type MarkupLineNumbersEditorProps = {
101+
lineNumbers?: MarkupLineNumbersConfig;
102+
};
103+
104+
export const MarkupLineNumbersEditor = memo<MarkupLineNumbersEditorProps>(
105+
function MarkupLineNumbersEditor({lineNumbers}) {
106+
const [fromLine, setFromLine] = useState<number | undefined>(
107+
lineNumbers?.initialSelectedLines?.from ?? 0,
108+
);
109+
const [toLine, setToLine] = useState<number | undefined>(
110+
lineNumbers?.initialSelectedLines?.to ?? 0,
111+
);
112+
const [lastClickedLine, setLastClickedLine] = useState<number | null>(null);
113+
114+
const markupLineNumbers: MarkupLineNumbersConfig | undefined = lineNumbers
115+
? {
116+
...lineNumbers,
117+
onLineClick: (line) => setLastClickedLine(line),
118+
}
119+
: undefined;
120+
121+
const editor = useMarkdownEditor(
122+
{
123+
initial: {
124+
mode: 'markup',
125+
markup: longMarkup,
126+
},
127+
markupConfig: {
128+
lineNumbers: markupLineNumbers,
129+
},
130+
},
131+
[],
132+
);
133+
134+
const handleHighlightLine = () => {
135+
if (typeof fromLine !== 'number' || Number.isNaN(fromLine)) return;
136+
if (typeof toLine !== 'number' || Number.isNaN(toLine)) return;
137+
138+
const cm = (editor as any).cm;
139+
if (cm) {
140+
cm.dispatch({effects: setHighlightedLine.of({from: fromLine, to: toLine})});
141+
}
142+
};
143+
144+
return (
145+
<PlaygroundLayout
146+
title="Markup Line Numbers"
147+
editor={editor}
148+
actions={({className}) =>
149+
lineNumbers?.highlightLines ? (
150+
<Flex gap="2" className={className} alignItems="center">
151+
<span>From line:</span>
152+
<NumberInput
153+
size="s"
154+
value={fromLine}
155+
onUpdate={setFromLine}
156+
min={0}
157+
style={{width: '72px'}}
158+
/>
159+
<span>To line:</span>
160+
<NumberInput
161+
size="s"
162+
value={toLine}
163+
onUpdate={setToLine}
164+
min={0}
165+
style={{width: '72px'}}
166+
/>
167+
<Button size="s" onClick={handleHighlightLine}>
168+
Highlight
169+
</Button>
170+
<Button
171+
size="s"
172+
view="outlined"
173+
onClick={() => {
174+
const cm = (editor as any).cm;
175+
if (cm) {
176+
cm.dispatch({effects: setHighlightedLine.of(null)});
177+
}
178+
}}
179+
>
180+
Clear
181+
</Button>
182+
{lastClickedLine !== null && (
183+
<span>Last clicked: line {lastClickedLine + 1}</span>
184+
)}
185+
</Flex>
186+
) : null
187+
}
188+
view={({className}) => (
189+
<MarkdownEditorView
190+
autofocus
191+
stickyToolbar
192+
settingsVisible
193+
editor={editor}
194+
className={className}
195+
/>
196+
)}
197+
/>
198+
);
199+
},
200+
);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type {Meta, StoryObj} from '@storybook/react';
2+
3+
import {MarkupLineNumbersEditor} from './Editor';
4+
5+
const meta: Meta<typeof MarkupLineNumbersEditor> = {
6+
title: 'Examples / Markup Line Numbers',
7+
component: MarkupLineNumbersEditor,
8+
};
9+
10+
export default meta;
11+
12+
type Story = StoryObj<typeof MarkupLineNumbersEditor>;
13+
14+
export const LineNumbersOnly: Story = {
15+
args: {
16+
lineNumbers: {enabled: true},
17+
},
18+
};
19+
LineNumbersOnly.storyName = 'Line Numbers Only';
20+
21+
export const HighlightLine: Story = {
22+
args: {
23+
lineNumbers: {enabled: true, highlightLines: true},
24+
},
25+
};
26+
HighlightLine.storyName = 'Highlight Line';
27+
28+
export const HighlightMultipleLines: Story = {
29+
args: {
30+
lineNumbers: {
31+
enabled: true,
32+
highlightLines: true,
33+
initialSelectedLines: {from: 5, to: 10},
34+
},
35+
},
36+
};
37+
HighlightMultipleLines.storyName = 'Highlight Multiple Lines';
38+
39+
export const ScrollToLine: Story = {
40+
args: {
41+
lineNumbers: {
42+
enabled: true,
43+
scrollToLine: 20,
44+
initialSelectedLines: {from: 20, to: 20},
45+
highlightLines: true,
46+
},
47+
},
48+
};
49+
ScrollToLine.storyName = 'Scroll to Line';
50+
51+
export const AllFeatures: Story = {
52+
args: {
53+
lineNumbers: {
54+
enabled: true,
55+
highlightLines: true,
56+
initialSelectedLines: {from: 20, to: 25},
57+
scrollToLine: 20,
58+
},
59+
},
60+
};
61+
AllFeatures.storyName = 'All Features';

packages/editor/src/bundle/Editor.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface EditorInt
6262
readonly mdOptions: Readonly<MarkdownEditorMdOptions>;
6363
readonly directiveSyntax: DirectiveSyntaxContext;
6464
readonly mobile: boolean;
65+
readonly markupConfig: MarkupConfig;
6566

6667
/** @internal used in demo for dev-tools */
6768
readonly _wysiwygView?: PMEditorView;
@@ -280,6 +281,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
280281
directiveSyntax: this.directiveSyntax,
281282
receiver: this,
282283
searchPanel: this.#markupConfig.searchPanel,
284+
lineNumbers: this.#markupConfig.lineNumbers,
283285
}),
284286
);
285287
}
@@ -310,6 +312,14 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
310312
return this.#mobile;
311313
}
312314

315+
get initialScrollToLine(): number | undefined {
316+
return this.#markupConfig.lineNumbers?.scrollToLine;
317+
}
318+
319+
get markupConfig(): MarkupConfig {
320+
return this.#markupConfig;
321+
}
322+
313323
constructor(opts: EditorOptions) {
314324
const {logger} = opts;
315325

packages/editor/src/bundle/MarkupEditorComponent.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ export const MarkupEditorComponent: React.FC<MarkupEditorComponentProps> =
3434
}
3535
}, [editor.markupEditor]);
3636

37+
// scroll to line on mount
38+
useEffect(() => {
39+
const scrollToLine = editor.markupConfig.lineNumbers?.scrollToLine;
40+
if (editor.markupConfig.lineNumbers?.enabled && scrollToLine !== undefined) {
41+
editor.moveCursor({line: scrollToLine});
42+
}
43+
}, []);
44+
3745
return (
3846
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
3947
<div

packages/editor/src/bundle/types.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {ReactNode} from 'react';
55
import type {MarkupString} from '../common';
66
import type {EscapeConfig, Extension} from '../core';
77
import type {Logger2} from '../logger';
8-
import type {CreateCodemirrorParams, YfmLangOptions} from '../markup';
8+
import type {CreateCodemirrorParams, LineRange, YfmLangOptions} from '../markup';
99
import type {FileUploadHandler} from '../utils';
1010
import type {DirectiveSyntaxContext, DirectiveSyntaxOption} from '../utils/directive';
1111
import type {ParseInsertedUrlAsImage} from '../utils/upload';
@@ -118,6 +118,19 @@ export type MarkdownEditorExperimentalOptions = {
118118
preserveMarkupFormatting?: boolean;
119119
};
120120

121+
export interface MarkupLineNumbersConfig {
122+
/** Show line numbers in the gutter. Default: false */
123+
enabled?: boolean;
124+
/** Enable line highlighting (clickable line numbers + highlight decoration). Default: false */
125+
highlightLines?: boolean;
126+
/** Initial line range to highlight on mount (0-based, inclusive) */
127+
initialSelectedLines?: LineRange;
128+
/** Called when user clicks on a line number (only when highlightLines is true). 0-based line number. */
129+
onLineClick?: (line: number) => void;
130+
/** 0-based line number to scroll to on mount in markup mode */
131+
scrollToLine?: number;
132+
}
133+
121134
export type MarkdownEditorMarkupConfig = {
122135
/**
123136
* Pass the rendering function to preview the markdown content.
@@ -164,6 +177,15 @@ export type MarkdownEditorMarkupConfig = {
164177
* @default true
165178
*/
166179
searchPanel?: boolean;
180+
/**
181+
* Line numbers configuration for the markup editor.
182+
*
183+
* When `enabled` is true, line numbers gutter is shown.
184+
* When `highlightLines` is true, line highlighting extension is enabled
185+
* (includes clickable line numbers and highlight decoration).
186+
* @default undefined
187+
*/
188+
lineNumbers?: MarkupLineNumbersConfig;
167189
};
168190

169191
// do not export this type

packages/editor/src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ export * from './view';
1313
export * from './utils';
1414
export * from './bundle';
1515

16-
export {DirectiveSyntaxFacet, ReactRendererFacet, getImageDimensions} from './markup';
16+
export type {LineRange, LineHighlightOptions} from './markup';
17+
export type {MarkupLineNumbersConfig} from './bundle/types';
18+
export {
19+
DirectiveSyntaxFacet,
20+
ReactRendererFacet,
21+
getImageDimensions,
22+
lineHighlight,
23+
setHighlightedLine,
24+
} from './markup';
1725
export * as MarkupCommands from './markup/commands';
1826
export * as MarkupHelpers from './markup/commands/helpers';
1927

0 commit comments

Comments
 (0)