Skip to content

Commit d6c4b6b

Browse files
authored
feat: coder editor enhancement + html editor split live view (#87)
* feat: enhance editor functionality with searchable language selector and formatting options - Introduced a new SearchableLanguageSelector component for improved language selection with search capabilities. - Updated Editor component to include a format button with tooltip for document formatting. - Enhanced styling for editor toolbar and language selector for better user experience. - Added responsive design elements to prevent overflow and improve layout consistency. * style: update Editor.scss for improved toolbar appearance - Commented out the background color for the toolbar to allow for a more flexible design. - Increased the border-top thickness for better visibility. - Adjusted padding-bottom to ensure sufficient space for the toolbar, enhancing overall layout consistency. * feat: enhance HTML editor integration and toolbar functionality - Replaced HtmlEditor component with a more versatile Editor component for HTML content rendering. - Introduced useHtmlEditor hook to manage HTML editor state and functionality. - Updated Editor.scss to improve toolbar layout and added new HTML-specific controls. - Enhanced Editor component to conditionally display HTML controls based on the selected language. - Refactored HtmlEditor to provide reusable controls for HTML editing, improving code organization and maintainability. * feat: implement split view for HTML editor with preview functionality - Added a split view mode to the HTML editor, allowing simultaneous editing and previewing of HTML content. - Introduced HtmlEditorSplitView component to manage the rendering of the editor and preview side by side. - Enhanced useHtmlEditor hook to support active state management and preview toggling. - Updated Editor and HtmlEditor components to integrate new split view features and improve user experience. - Refactored styles in Editor.scss and HtmlEditor.scss to accommodate the new layout and ensure responsive design.
1 parent e43f771 commit d6c4b6b

File tree

10 files changed

+905
-178
lines changed

10 files changed

+905
-178
lines changed

src/frontend/src/CustomEmbeddableRenderer.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
Dashboard,
88
StateIndicator,
99
ControlButton,
10-
HtmlEditor,
1110
Editor,
1211
Terminal,
1312
} from './pad';
@@ -28,7 +27,11 @@ export const renderCustomEmbeddable = (
2827

2928
switch (path) {
3029
case 'html':
31-
content = <HtmlEditor element={element} appState={appState} excalidrawAPI={excalidrawAPI} />;
30+
content = <Editor
31+
element={element}
32+
language="html"
33+
excalidrawAPI={excalidrawAPI}
34+
/>;
3235
title = "HTML Editor";
3336
break;
3437
case 'editor':

src/frontend/src/pad/editors/Editor.scss

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,105 @@
11
.editor {
22
&__wrapper {
3-
display: flex;
4-
flex-direction: column;
3+
position: relative; /* For absolute positioning of toolbar */
54
height: 100%;
65
width: 100%;
6+
overflow: hidden; /* Prevent overflow issues */
7+
border-bottom-left-radius: 20px;
8+
border-bottom-right-radius: 20px;
9+
10+
&--split {
11+
.monaco-editor {
12+
width: 50% !important; /* Force Monaco editor to resize */
13+
}
14+
15+
.editor__container {
16+
width: 50% !important; /* Force container to resize */
17+
}
18+
19+
.editor__toolbar {
20+
right: 50%; /* Limit toolbar width to match the editor in split view */
21+
}
22+
}
723
}
824

925
&__toolbar {
1026
display: flex;
11-
justify-content: right;
27+
justify-content: flex-end; /* Default justify to the right when no HTML controls */
1228
padding: 8px;
29+
position: absolute; /* Position at bottom */
30+
bottom: 0;
31+
left: 0;
32+
right: 0;
33+
z-index: 10; /* Keep toolbar above other elements */
34+
// background-color: #191919; /* Match editor background */
35+
border-top: 2px solid #3c3c3c;
36+
}
37+
38+
&__toolbar-right {
39+
display: flex;
40+
align-items: center;
41+
gap: 8px; /* Add spacing between toolbar items */
42+
43+
.excalidraw-tooltip-wrapper {
44+
height: 100%;
45+
display: flex;
46+
align-items: center;
47+
}
48+
}
49+
50+
&__html-controls {
51+
display: flex;
52+
align-items: center;
53+
gap: 8px;
54+
margin-right: auto; /* Push to the left side */
55+
56+
.html-editor__label {
57+
font-size: 12px;
58+
color: #e0e0e0;
59+
display: flex;
60+
align-items: center;
61+
gap: 4px;
62+
}
63+
64+
.html-editor__button {
65+
padding: 6px 12px;
66+
background: #5294f6;
67+
color: white;
68+
border: none;
69+
border-radius: 4px;
70+
cursor: pointer;
71+
font-size: 12px;
72+
73+
&:hover {
74+
background: #4285e7;
75+
}
76+
}
77+
}
78+
79+
&__format-button {
80+
background-color: #252526;
81+
color: #cccccc;
82+
border: 1px solid #3c3c3c;
83+
border-radius: 7px;
84+
padding: 4px 8px;
85+
cursor: pointer;
86+
display: flex;
87+
align-items: center;
88+
justify-content: center;
89+
height: 100%;
90+
&:hover {
91+
background-color: #2a2d2e;
92+
color: #ffffff;
93+
}
94+
95+
&:focus {
96+
outline: none;
97+
border-color: #007fd4;
98+
}
1399
}
14100

15101
&__language-selector {
102+
display: flex;
16103
margin-right: 10px;
17104
}
18105

@@ -35,8 +122,106 @@
35122
color: #cccccc;
36123
}
37124
}
125+
126+
&__searchable-language-container {
127+
display: flex;
128+
align-items: center;
129+
background-color: #252526;
130+
border: 1px solid #3c3c3c;
131+
border-radius: 7px;
132+
max-width: 150px;
133+
position: relative;
134+
135+
&:focus-within {
136+
border-color: #007fd4;
137+
}
138+
}
139+
140+
&__searchable-language-input {
141+
background-color: transparent;
142+
color: #cccccc;
143+
border: none;
144+
padding: 4px 8px;
145+
font-size: 12px;
146+
width: 100%;
147+
148+
&:focus {
149+
outline: none;
150+
}
151+
152+
&::placeholder {
153+
color: #cccccc;
154+
opacity: 0.8;
155+
}
156+
}
157+
158+
&__searchable-language-toggle {
159+
background: none;
160+
border: none;
161+
color: #cccccc;
162+
cursor: pointer;
163+
display: flex;
164+
align-items: center;
165+
justify-content: center;
166+
padding: 0 6px;
167+
transition: transform 0.2s ease;
168+
169+
&:hover {
170+
color: #ffffff;
171+
}
172+
173+
&:focus {
174+
outline: none;
175+
}
176+
}
177+
178+
&__searchable-language-dropdown {
179+
position: absolute;
180+
bottom: 100%; /* Position above instead of below */
181+
left: 0;
182+
right: 0;
183+
max-height: 200px;
184+
overflow-y: auto;
185+
background-color: #252526;
186+
border: 1px solid #3c3c3c;
187+
border-radius: 7px;
188+
margin-bottom: 4px; /* Margin at bottom instead of top */
189+
z-index: 20; /* Higher z-index to ensure it's above the toolbar */
190+
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.3); /* Shadow adjusted for upward direction */
191+
}
192+
193+
&__searchable-language-option {
194+
padding: 4px 8px;
195+
font-size: 12px;
196+
color: #cccccc;
197+
cursor: pointer;
198+
199+
&:hover {
200+
background-color: #2a2d2e;
201+
}
202+
203+
&--highlighted {
204+
background-color: #04395e;
205+
}
206+
207+
&--selected {
208+
color: #ffffff;
209+
font-weight: 500;
210+
}
211+
}
212+
213+
&__searchable-language-no-results {
214+
padding: 4px 8px;
215+
font-size: 12px;
216+
color: #cccccc;
217+
font-style: italic;
218+
text-align: center;
219+
}
38220

39221
&__container {
40-
flex: 1;
222+
height: 100%;
223+
width: 100%;
224+
padding-bottom: 60px; /* Make room for the toolbar */
225+
box-sizing: border-box;
41226
}
42227
}

src/frontend/src/pad/editors/Editor.tsx

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
11
import React, { useRef, useState, useEffect, useCallback } from 'react';
22
import MonacoEditor from '@monaco-editor/react';
3-
import LanguageSelector from './LanguageSelector';
3+
import { Tooltip, updateTooltipPosition, getTooltipDiv } from '@atyrode/excalidraw';
4+
import SearchableLanguageSelector from './SearchableLanguageSelector';
5+
import { useHtmlEditor, HtmlEditorControls, defaultHtml, HtmlEditorSplitView } from './HtmlEditor';
46
import './Editor.scss';
57

8+
// Custom tooltip wrapper that positions the tooltip at the top
9+
const TopTooltip: React.FC<{label: string, children: React.ReactNode}> = ({ label, children }) => {
10+
const handlePointerEnter = (event: React.PointerEvent<HTMLDivElement>) => {
11+
const tooltip = getTooltipDiv();
12+
tooltip.classList.add("excalidraw-tooltip--visible");
13+
tooltip.textContent = label;
14+
15+
const itemRect = event.currentTarget.getBoundingClientRect();
16+
updateTooltipPosition(tooltip, itemRect, "top");
17+
};
18+
19+
const handlePointerLeave = () => {
20+
getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
21+
};
22+
23+
return (
24+
<div
25+
className="excalidraw-tooltip-wrapper"
26+
onPointerEnter={handlePointerEnter}
27+
onPointerLeave={handlePointerLeave}
28+
>
29+
{children}
30+
</div>
31+
);
32+
};
33+
634
interface EditorProps {
735
defaultValue?: string;
836
language?: string;
@@ -21,7 +49,7 @@ interface EditorProps {
2149

2250
const Editor: React.FC<EditorProps> = ({
2351
defaultValue = '',
24-
language = 'javascript',
52+
language = 'plaintext',
2553
theme = 'vs-dark',
2654
height = '100%',
2755
options = {
@@ -259,25 +287,84 @@ const Editor: React.FC<EditorProps> = ({
259287
}
260288
};
261289

290+
// Format document function
291+
const formatDocument = () => {
292+
if (editorRef.current) {
293+
// Trigger Monaco's format document action
294+
editorRef.current.getAction('editor.action.formatDocument')?.run();
295+
}
296+
};
297+
298+
// Check if the language is HTML
299+
const isHtml = currentLanguage === 'html';
300+
301+
// Always initialize HTML editor hooks, but pass isActive flag
302+
const htmlEditor = useHtmlEditor(
303+
element,
304+
editorRef,
305+
excalidrawAPI,
306+
isHtml
307+
);
308+
309+
// Determine if we should show the split view
310+
const showSplitView = isHtml && !htmlEditor.createNew && htmlEditor.showPreview;
311+
262312
return (
263-
<div className="editor__wrapper">
313+
<div className={`editor__wrapper ${showSplitView ? 'editor__wrapper--split' : ''}`}>
264314
<MonacoEditor
265315
height={height}
266316
language={currentLanguage}
267-
defaultValue={defaultValue}
317+
defaultValue={defaultValue || (isHtml ? defaultHtml : '')}
268318
theme={theme}
269319
options={options}
270320
onMount={handleEditorDidMount}
271321
onChange={handleEditorChange}
272322
className={className}
273323
/>
324+
325+
{/* Render the HTML preview in split view mode */}
326+
{showSplitView && (
327+
<HtmlEditorSplitView
328+
editorContent={contentRef.current || ''}
329+
previewContent={htmlEditor.previewContent}
330+
showPreview={htmlEditor.showPreview}
331+
/>
332+
)}
274333
{showLanguageSelector && (
275334
<div className="editor__toolbar">
276-
<LanguageSelector
277-
value={currentLanguage}
278-
onChange={handleLanguageChange}
279-
className="editor__language-selector"
280-
/>
335+
{/* Show HTML-specific controls when language is HTML */}
336+
{isHtml && (
337+
<div className="editor__html-controls">
338+
<HtmlEditorControls
339+
createNew={htmlEditor.createNew}
340+
setCreateNew={htmlEditor.setCreateNew}
341+
applyHtml={htmlEditor.applyHtml}
342+
showPreview={htmlEditor.showPreview}
343+
togglePreview={htmlEditor.togglePreview}
344+
/>
345+
</div>
346+
)}
347+
348+
{/* Group format button and language selector together on the right */}
349+
<div className="editor__toolbar-right">
350+
<TopTooltip label="Format" children={
351+
<button
352+
className="editor__format-button"
353+
onClick={formatDocument}
354+
aria-label="Format Document"
355+
>
356+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
357+
<path d="M2 4H14M4 8H12M6 12H10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
358+
</svg>
359+
</button>
360+
} />
361+
362+
<SearchableLanguageSelector
363+
value={currentLanguage}
364+
onChange={handleLanguageChange}
365+
className="editor__language-selector"
366+
/>
367+
</div>
281368
</div>
282369
)}
283370
</div>

0 commit comments

Comments
 (0)