Skip to content

Commit 8e4ef3e

Browse files
authored
feat: support folding text fragments in code editor (#4676)
Closes #3636 Enabled folding for dialog editors only to avoid bloating space in settings panel. <img width="905" alt="Screenshot 2024-12-30 at 19 42 03" src="https://github.com/user-attachments/assets/39a500f1-7d4f-407d-a7df-a1f47e15f69c" /> <img width="918" alt="Screenshot 2024-12-30 at 19 42 55" src="https://github.com/user-attachments/assets/fe3b4c65-ec95-40c5-a114-e5fef2e02584" />
1 parent ed36cf8 commit 8e4ef3e

File tree

7 files changed

+121
-104
lines changed

7 files changed

+121
-104
lines changed

apps/builder/app/builder/shared/code-editor-base.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
historyKeymap,
2626
indentWithTab,
2727
} from "@codemirror/commands";
28-
import { syntaxHighlighting } from "@codemirror/language";
28+
import { foldGutter, syntaxHighlighting } from "@codemirror/language";
2929
import {
3030
theme,
3131
textVariants,
@@ -40,6 +40,7 @@ import {
4040
FloatingPanel,
4141
} from "@webstudio-is/design-system";
4242
import { MaximizeIcon } from "@webstudio-is/icons";
43+
import { ChevronDownIcon, ChevronRightIcon } from "@webstudio-is/icons/svg";
4344
import { solarizedLight } from "./code-highlight";
4445

4546
// This undocumented flag is required to keep contenteditable fields editable after the first activation of EditorView.
@@ -121,6 +122,10 @@ const editorContentStyle = css({
121122
textDecoration: "underline wavy red",
122123
backgroundColor: "rgba(255, 0, 0, 0.1)",
123124
},
125+
".cm-gutters": {
126+
backgroundColor: "transparent",
127+
border: 0,
128+
},
124129
});
125130

126131
const shortcutStyle = css({
@@ -188,6 +193,17 @@ const keyBindings = [
188193
indentWithTab,
189194
];
190195

196+
export const foldGutterExtension = foldGutter({
197+
markerDOM: (isOpen) => {
198+
const div = document.createElement("div");
199+
div.style.width = "16px";
200+
div.style.height = "16px";
201+
div.style.cursor = "pointer";
202+
div.innerHTML = isOpen ? ChevronDownIcon : ChevronRightIcon;
203+
return div;
204+
},
205+
});
206+
191207
export type EditorApi = {
192208
replaceSelection: (string: string) => void;
193209
focus: () => void;

apps/builder/app/builder/shared/code-editor.tsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
EditorDialog,
2828
EditorDialogButton,
2929
EditorDialogControl,
30+
foldGutterExtension,
3031
getMinMaxHeightVars,
3132
} from "./code-editor-base";
3233

@@ -96,6 +97,11 @@ export const CodeEditor = forwardRef<
9697
return [];
9798
}, [lang]);
9899

100+
const dialogExtensions = useMemo(
101+
() => [...extensions, foldGutterExtension],
102+
[extensions]
103+
);
104+
99105
// prevent clicking on autocomplete options propagating to body
100106
// and closing dialogs and popovers
101107
useEffect(() => {
@@ -113,14 +119,19 @@ export const CodeEditor = forwardRef<
113119
document.removeEventListener("pointerdown", handlePointerDown, options);
114120
};
115121
}, []);
116-
const content = (
117-
<EditorContent {...editorContentProps} extensions={extensions} />
118-
);
119122
return (
120123
<div className={wrapperStyle()} ref={ref}>
121124
<EditorDialogControl>
122-
{content}
123-
<EditorDialog title={title} content={content}>
125+
{<EditorContent {...editorContentProps} extensions={extensions} />}
126+
<EditorDialog
127+
title={title}
128+
content={
129+
<EditorContent
130+
{...editorContentProps}
131+
extensions={dialogExtensions}
132+
/>
133+
}
134+
>
124135
<EditorDialogButton />
125136
</EditorDialog>
126137
</EditorDialogControl>

apps/builder/app/builder/shared/expression-editor.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
EditorDialog,
4040
EditorDialogButton,
4141
EditorDialogControl,
42+
foldGutterExtension,
4243
type EditorApi,
4344
} from "./code-editor-base";
4445

@@ -518,7 +519,7 @@ export const ExpressionEditor = ({
518519
// by spliting into separate component which is invoked
519520
// only when dialog content is rendered
520521
const ValuePreviewEditor = ({ value }: { value: unknown }) => {
521-
const extensions = useMemo(() => [javascript({})], []);
522+
const extensions = useMemo(() => [javascript({}), foldGutterExtension], []);
522523
return (
523524
<EditorContent
524525
readOnly={true}

apps/builder/package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
},
1919
"dependencies": {
2020
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
21-
"@codemirror/autocomplete": "^6.18.1",
21+
"@codemirror/autocomplete": "^6.18.4",
2222
"@codemirror/commands": "^6.7.1",
23-
"@codemirror/lang-css": "^6.3.0",
23+
"@codemirror/lang-css": "^6.3.1",
2424
"@codemirror/lang-html": "^6.4.9",
2525
"@codemirror/lang-javascript": "^6.2.2",
26-
"@codemirror/lang-markdown": "^6.3.0",
27-
"@codemirror/language": "^6.10.3",
28-
"@codemirror/lint": "^6.8.2",
29-
"@codemirror/state": "^6.4.1",
30-
"@codemirror/view": "^6.34.1",
26+
"@codemirror/lang-markdown": "^6.3.1",
27+
"@codemirror/language": "^6.10.8",
28+
"@codemirror/lint": "^6.8.4",
29+
"@codemirror/state": "^6.5.0",
30+
"@codemirror/view": "^6.36.1",
3131
"@floating-ui/dom": "^1.6.11",
3232
"@fontsource-variable/inter": "^5.0.20",
3333
"@fontsource-variable/manrope": "^5.0.20",

packages/jsx-utils/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@webstudio-is/css-data": "workspace:*",
3131
"@webstudio-is/react-sdk": "workspace:*",
3232
"@webstudio-is/sdk": "workspace:*",
33-
"acorn": "^8.12.1",
33+
"acorn": "^8.14.0",
3434
"acorn-jsx": "^5.3.2",
3535
"json5": "^2.2.3",
3636
"zod": "^3.22.4"

packages/sdk/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"dependencies": {
3939
"@webstudio-is/css-engine": "workspace:*",
4040
"@webstudio-is/fonts": "workspace:*",
41-
"acorn": "^8.12.1",
41+
"acorn": "^8.14.0",
4242
"acorn-walk": "^8.3.4",
4343
"reserved-identifiers": "^1.0.0",
4444
"type-fest": "^4.31.0",

0 commit comments

Comments
 (0)