From 65e28254e7d685028ec7506457b09b4ca3474dd1 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 5 May 2023 14:53:22 -0400 Subject: [PATCH 01/12] migrate to monaco on component code view --- .bitmap | 13 ++ components/ui/code-view/code-view.module.scss | 84 +++++++++ components/ui/code-view/code-view.tsx | 175 ++++++++++++++++++ components/ui/code-view/index.ts | 2 + .../component-compare/component-compare.tsx | 2 + .../code-compare-view/code-compare-view.tsx | 6 +- scopes/code/ui/code-compare/code-compare.tsx | 2 +- scopes/code/ui/code-editor/code-editor.tsx | 44 +++++ scopes/code/ui/code-editor/index.ts | 2 + .../component-compare.ui.runtime.tsx | 10 +- workspace.jsonc | 1 - 11 files changed, 335 insertions(+), 6 deletions(-) create mode 100644 components/ui/code-view/code-view.module.scss create mode 100644 components/ui/code-view/code-view.tsx create mode 100644 components/ui/code-view/index.ts create mode 100644 scopes/code/ui/code-editor/code-editor.tsx create mode 100644 scopes/code/ui/code-editor/index.ts diff --git a/.bitmap b/.bitmap index bec158014343..80a4df5708f8 100644 --- a/.bitmap +++ b/.bitmap @@ -1317,6 +1317,13 @@ "mainFile": "index.ts", "rootDir": "scopes/code/ui/code-compare" }, + "ui/code-editor": { + "scope": "", + "version": "", + "defaultScope": "teambit.code", + "mainFile": "index.ts", + "rootDir": "scopes/code/ui/code-editor" + }, "ui/code-tab-page": { "scope": "teambit.code", "version": "0.0.614", @@ -1329,6 +1336,12 @@ "mainFile": "index.ts", "rootDir": "scopes/code/ui/code-tab-tree" }, + "ui/code-view": { + "scope": "teambit.code", + "version": "5e2b49420c500d4d88e1b05aa232061abc0fa0a7", + "mainFile": "index.ts", + "rootDir": "components/ui/code-view" + }, "ui/compare/lane-compare": { "scope": "teambit.lanes", "version": "0.0.78", diff --git a/components/ui/code-view/code-view.module.scss b/components/ui/code-view/code-view.module.scss new file mode 100644 index 000000000000..3ed80c348683 --- /dev/null +++ b/components/ui/code-view/code-view.module.scss @@ -0,0 +1,84 @@ +@import '~@teambit/ui-foundation.ui.constants.z-indexes/z-indexes.module.scss'; + +.codeViewTitle { + display: flex; + height: 40px; +} + +.img { + width: 20px; + margin-right: 10px; +} + +.fileName { + display: flex; + align-items: baseline; +} + +.componentCodeViewContainer { + display: flex; + width: calc(100% - 80px); + flex: 1; + padding: 24px 40px; + flex-direction: column; + transition: min-height 0.4s ease-in-out; + height: calc(100% - 48px); + min-height: calc(100% - 48px); + ˝ &.loading { + padding: 0px 24px; + } +} + +.componentCodeEditorContainer { + display: flex; + flex: 1; + transition: min-height 0.4s ease-in-out; + position: relative; + background: var(--on-surface-neutral-low-color, #282828) !important; + height: calc(100% - 40px); + min-height: calc(100% - 40px); + + > section { + overflow: hidden; + } +} + +.loader { + width: calc(100% - 48px); + color: var(--on-surface-neutral-low-color, #282828); + background-image: linear-gradient(to right, currentColor 0%, #2b2b2b 30%, #2b2b2b 50%, currentColor 100%); + opacity: 1; + visibility: visible; + transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + position: absolute; + height: 220px; + + > div { + background-image: linear-gradient(to right, currentColor 0%, #2b2b2b 30%, #2b2b2b 50%, currentColor 100%); + visibility: visible; + opacity: 1; + transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + } +} + +.hideLoader, +.hideLoader > div { + opacity: 0; + pointer-events: none; + visibility: hidden; +} + +.themeContainer { + height: 100%; + min-height: 100%; + display: flex; + width: 100%; + flex: 1; + + > div { + width: 100%; + height: 100%; + min-height: 100%; + display: flex; + } +} diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx new file mode 100644 index 000000000000..3b702bc24f72 --- /dev/null +++ b/components/ui/code-view/code-view.tsx @@ -0,0 +1,175 @@ +import { H1 } from '@teambit/documenter.ui.heading'; +import classNames from 'classnames'; +import React, { HTMLAttributes, useMemo } from 'react'; +import { OnMount, Monaco } from '@monaco-editor/react'; +// import { CodeSnippet } from '@teambit/documenter.ui.code-snippet'; +import { useFileContent } from '@teambit/code.ui.queries.get-file-content'; +import { CodeEditor } from '@teambit/code.ui.code-editor'; +import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton'; +import { ThemeSwitcher } from '@teambit/design.themes.theme-toggler'; +import { DarkTheme } from '@teambit/design.themes.dark-theme'; +// import markDownSyntax from 'react-syntax-highlighter/dist/esm/languages/prism/markdown'; +// import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light'; +import { staticStorageUrl } from '@teambit/base-ui.constants.storage'; +import { ComponentID } from '@teambit/component'; +import styles from './code-view.module.scss'; + +export type CodeViewProps = { + componentId: ComponentID; + currentFile?: string; + currentFileContent?: string; + icon?: string; + loading?: boolean; + codeSnippetClassName?: string; +} & HTMLAttributes; + +// SyntaxHighlighter.registerLanguage('md', markDownSyntax); + +// a translation list of specific monaco languages that are not the same as their file ending. +const languageOverrides = { + ts: 'typescript', + tsx: 'typescript', + js: 'javascript', + jsx: 'javascript', + mdx: 'markdown', + md: 'markdown', +}; + +export function CodeView({ + className, + componentId, + currentFile, + icon, + currentFileContent, + // codeSnippetClassName, + loading: loadingFromProps, +}: CodeViewProps) { + const monacoRef = React.useRef<{ + editor: any; + monaco: Monaco; + }>(); + + const { fileContent: downloadedFileContent, loading: loadingFileContent } = useFileContent( + componentId, + currentFile, + !!currentFileContent + ); + + const handleEditorDidMount: OnMount = (editor, monaco) => { + /** + * disable syntax check + * ts cant validate all types because imported files aren't available to the editor + */ + monacoRef.current = { monaco, editor }; + if (monacoRef.current) { + monacoRef.current.monaco.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ + noSemanticValidation: true, + noSyntaxValidation: true, + }); + } + + monaco.editor.defineTheme('bit', { + base: 'vs-dark', + inherit: true, + rules: [], + colors: { + 'scrollbar.shadow': '#222222', + 'diffEditor.insertedTextBackground': '#1C4D2D', + 'diffEditor.removedTextBackground': '#761E24', + 'editor.selectionBackground': '#5A5A5A', + 'editor.overviewRulerBorder': '#6a57fd', + 'editor.lineHighlightBorder': '#6a57fd', + }, + }); + monaco.editor.setTheme('bit'); + }; + + const loading = loadingFromProps || loadingFileContent; + const fileContent = currentFileContent || downloadedFileContent; + const title = useMemo(() => currentFile?.split('/').pop(), [currentFile]); + const language = useMemo(() => { + if (!currentFile) return languageOverrides.ts; + const fileEnding = currentFile?.split('.').pop(); + return languageOverrides[fileEnding || ''] || fileEnding; + }, [currentFile]); + + const codeEditor = useMemo( + () => ( + } + /> + ), + [fileContent] + ); + + if (!fileContent && !loading && currentFile) return ; + + // return ( + //
+ //

+ // {currentFile && } + // {title} + //

+ // + // {fileContent || ''} + // + //
+ // ); + + return ( +
+
+

+ {currentFile && } + {title} +

+
+
+ + + {loading ? null : codeEditor} + +
+
+ ); +} + +function EmptyCodeView() { + return ( +
+ +
Nothing to show
+
+ ); +} + +export function CodeViewLoader({ className, ...rest }: React.HTMLAttributes) { + return ; +} diff --git a/components/ui/code-view/index.ts b/components/ui/code-view/index.ts new file mode 100644 index 000000000000..3dd57f347c74 --- /dev/null +++ b/components/ui/code-view/index.ts @@ -0,0 +1,2 @@ +export { CodeView } from './code-view'; +export type { CodeViewProps } from './code-view'; diff --git a/components/ui/component-compare/component-compare/component-compare.tsx b/components/ui/component-compare/component-compare/component-compare.tsx index ea837623333b..ca3308231ae0 100644 --- a/components/ui/component-compare/component-compare/component-compare.tsx +++ b/components/ui/component-compare/component-compare/component-compare.tsx @@ -51,6 +51,7 @@ export function ComponentCompare(props: ComponentCompareProps) { Loader = CompareLoader, baseContext, compareContext, + isFullScreen, ...rest } = props; const baseVersion = useCompareQueryParam('baseVersion'); @@ -148,6 +149,7 @@ export function ComponentCompare(props: ComponentCompareProps) { compareContext, fieldCompareDataByName, fileCompareDataByName, + isFullScreen, }; const changes = diff --git a/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx b/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx index ba432b679081..83049aea450c 100644 --- a/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx +++ b/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx @@ -49,11 +49,12 @@ export function CodeCompareView({ editor: any; monaco: Monaco; }>(); - + const changedLinesRef = useRef(0); const { baseId, compareId, modifiedFileContent, originalFileContent, modifiedPath, originalPath, loading } = useCodeCompare({ fileName, }); + const containerRef = useRef(null); const componentCompareContext = useComponentCompare(); const getDefaultView: () => EditorViewMode = () => { @@ -78,7 +79,6 @@ export function CodeCompareView({ const fileEnding = fileName?.split('.').pop(); return languageOverrides[fileEnding || ''] || fileEnding; }, [fileName]); - const containerRef = useRef(null); const isFullScreen = !!componentCompareContext?.isFullScreen; useEffect(() => { @@ -114,7 +114,7 @@ export function CodeCompareView({ return displayedLines; }; - const changedLinesRef = useRef(0); + const updateChangedLines = () => { if (!monacoRef.current) return; diff --git a/scopes/code/ui/code-compare/code-compare.tsx b/scopes/code/ui/code-compare/code-compare.tsx index b5d481028c2a..14ba53826134 100644 --- a/scopes/code/ui/code-compare/code-compare.tsx +++ b/scopes/code/ui/code-compare/code-compare.tsx @@ -45,7 +45,7 @@ export function CodeCompare({ fileIconSlot, className, CodeView = CodeCompareVie const anyFileHasDiffStatus = useRef(false); const fileTree = useMemo(() => { - const allFiles = uniq(baseFileTree.concat(compareFileTree)); + const allFiles = uniq(baseFileTree.concat(compareFileTree)); anyFileHasDiffStatus.current = false; // sort by diff status return !fileCompareDataByName diff --git a/scopes/code/ui/code-editor/code-editor.tsx b/scopes/code/ui/code-editor/code-editor.tsx new file mode 100644 index 000000000000..c198bdac5555 --- /dev/null +++ b/scopes/code/ui/code-editor/code-editor.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Editor, { OnMount } from '@monaco-editor/react'; +import { darkMode } from '@teambit/base-ui.theme.dark-theme'; + +export type CodeEditorProps = { + language: string; + handleEditorDidMount: OnMount; + Loader: React.ReactNode; + fileContent?: string; + originalFileContent?: string; + filePath?: string; +}; + +export function CodeEditor({ fileContent, filePath, language, handleEditorDidMount, Loader }: CodeEditorProps) { + return ( + + ); +} diff --git a/scopes/code/ui/code-editor/index.ts b/scopes/code/ui/code-editor/index.ts new file mode 100644 index 000000000000..31ddb86fa07d --- /dev/null +++ b/scopes/code/ui/code-editor/index.ts @@ -0,0 +1,2 @@ +export { CodeEditor } from './code-editor'; +export type { CodeEditorProps } from './code-editor'; diff --git a/scopes/component/component-compare/component-compare.ui.runtime.tsx b/scopes/component/component-compare/component-compare.ui.runtime.tsx index 0704d1180f88..d99c4c1e2d34 100644 --- a/scopes/component/component-compare/component-compare.ui.runtime.tsx +++ b/scopes/component/component-compare/component-compare.ui.runtime.tsx @@ -35,7 +35,15 @@ export class ComponentCompareUI { const routes = props?.routes || (() => flatten(this.routeSlot.values())); const host = props?.host || this.host; - return ; + return ( + + ); }; getAspectsComparePage = () => { diff --git a/workspace.jsonc b/workspace.jsonc index 95771d824ece..57143d313474 100644 --- a/workspace.jsonc +++ b/workspace.jsonc @@ -82,7 +82,6 @@ "@teambit/bvm.config": "0.2.2", "@teambit/capsule": "0.0.12", "@teambit/code.ui.code-compare-section": "^0.0.5", - "@teambit/code.ui.code-view": "^0.0.508", "@teambit/code.ui.dependency-tree": "^0.0.546", "@teambit/code.ui.hooks.use-code-params": "^0.0.496", "@teambit/code.ui.object-formatter": "0.0.1", From a440d5ce63991c2bddb97cc3d7c8ef01abdb3b86 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 5 May 2023 14:56:01 -0400 Subject: [PATCH 02/12] clean up --- components/ui/code-view/code-view.tsx | 44 ++------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx index 3b702bc24f72..75ea4578f653 100644 --- a/components/ui/code-view/code-view.tsx +++ b/components/ui/code-view/code-view.tsx @@ -2,14 +2,11 @@ import { H1 } from '@teambit/documenter.ui.heading'; import classNames from 'classnames'; import React, { HTMLAttributes, useMemo } from 'react'; import { OnMount, Monaco } from '@monaco-editor/react'; -// import { CodeSnippet } from '@teambit/documenter.ui.code-snippet'; import { useFileContent } from '@teambit/code.ui.queries.get-file-content'; import { CodeEditor } from '@teambit/code.ui.code-editor'; import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton'; import { ThemeSwitcher } from '@teambit/design.themes.theme-toggler'; import { DarkTheme } from '@teambit/design.themes.dark-theme'; -// import markDownSyntax from 'react-syntax-highlighter/dist/esm/languages/prism/markdown'; -// import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light'; import { staticStorageUrl } from '@teambit/base-ui.constants.storage'; import { ComponentID } from '@teambit/component'; import styles from './code-view.module.scss'; @@ -23,8 +20,6 @@ export type CodeViewProps = { codeSnippetClassName?: string; } & HTMLAttributes; -// SyntaxHighlighter.registerLanguage('md', markDownSyntax); - // a translation list of specific monaco languages that are not the same as their file ending. const languageOverrides = { ts: 'typescript', @@ -108,50 +103,15 @@ export function CodeView({ if (!fileContent && !loading && currentFile) return ; - // return ( - //
- //

- // {currentFile && } - // {title} - //

- // - // {fileContent || ''} - // - //
- // ); - return ( -
+

{currentFile && } {title}

-
+
{loading ? null : codeEditor} From 1e1141ac8eb7d9aebeccfdd5dc06edfa533e2765 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 5 May 2023 15:44:38 -0400 Subject: [PATCH 03/12] fix types --- scopes/code/ui/code-editor/code-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scopes/code/ui/code-editor/code-editor.tsx b/scopes/code/ui/code-editor/code-editor.tsx index c198bdac5555..6ba07b82179f 100644 --- a/scopes/code/ui/code-editor/code-editor.tsx +++ b/scopes/code/ui/code-editor/code-editor.tsx @@ -30,7 +30,7 @@ export function CodeEditor({ fileContent, filePath, language, handleEditorDidMou overviewRulerLanes: 0, overviewRulerBorder: false, wordWrap: 'off', - wrappingStrategy: 'undefined', + wrappingStrategy: undefined, fixedOverflowWidgets: true, renderLineHighlight: 'none', lineHeight: 20, From d39dad15894934a1ee252e089394ad326ddc46c2 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Thu, 18 May 2023 16:50:06 -0400 Subject: [PATCH 04/12] implement jsx tokenizer --- components/ui/code-view/code-view.module.scss | 2 +- components/ui/code-view/code-view.tsx | 46 +++++-- .../ui/code-view/monaco-language-init.ts | 116 ++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 components/ui/code-view/monaco-language-init.ts diff --git a/components/ui/code-view/code-view.module.scss b/components/ui/code-view/code-view.module.scss index 3ed80c348683..9523b653e9eb 100644 --- a/components/ui/code-view/code-view.module.scss +++ b/components/ui/code-view/code-view.module.scss @@ -44,7 +44,7 @@ } .loader { - width: calc(100% - 48px); + width: 100%; color: var(--on-surface-neutral-low-color, #282828); background-image: linear-gradient(to right, currentColor 0%, #2b2b2b 30%, #2b2b2b 50%, currentColor 100%); opacity: 1; diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx index 75ea4578f653..1ca9745105a3 100644 --- a/components/ui/code-view/code-view.tsx +++ b/components/ui/code-view/code-view.tsx @@ -10,6 +10,7 @@ import { DarkTheme } from '@teambit/design.themes.dark-theme'; import { staticStorageUrl } from '@teambit/base-ui.constants.storage'; import { ComponentID } from '@teambit/component'; import styles from './code-view.module.scss'; +import { setupLanguage } from './monaco-language-init'; export type CodeViewProps = { componentId: ComponentID; @@ -17,7 +18,6 @@ export type CodeViewProps = { currentFileContent?: string; icon?: string; loading?: boolean; - codeSnippetClassName?: string; } & HTMLAttributes; // a translation list of specific monaco languages that are not the same as their file ending. @@ -36,7 +36,6 @@ export function CodeView({ currentFile, icon, currentFileContent, - // codeSnippetClassName, loading: loadingFromProps, }: CodeViewProps) { const monacoRef = React.useRef<{ @@ -56,17 +55,43 @@ export function CodeView({ * ts cant validate all types because imported files aren't available to the editor */ monacoRef.current = { monaco, editor }; - if (monacoRef.current) { - monacoRef.current.monaco.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ - noSemanticValidation: true, - noSyntaxValidation: true, - }); - } + + // monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + // noSemanticValidation: true, + // noSyntaxValidation: true, + // }); + + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + target: monaco.languages.typescript.ScriptTarget.Latest, + allowNonTsExtensions: true, + moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, + module: monaco.languages.typescript.ModuleKind.CommonJS, + jsx: monaco.languages.typescript.JsxEmit.React, + noEmit: true, + typeRoots: ['node_modules/@types'], + jsxFactory: 'JSXAlone.createElement', + reactNamespace: 'React', + esModuleInterop: true, + }); + + setupLanguage(monaco, 'typescript').catch(() => {}); monaco.editor.defineTheme('bit', { base: 'vs-dark', inherit: true, - rules: [], + rules: [ + { token: 'delimiter', foreground: 'ffffff' }, + { token: 'delimiter.angle', foreground: '808080' }, + { token: 'tag.dom', foreground: '569cd6' }, + { token: 'tag.custom', foreground: '4ec9b0' }, + { token: 'delimiter.bracket', foreground: 'd7ba7d' }, + { token: 'attribute.key', foreground: '9cdcfe' }, + { token: 'attribute.value', foreground: 'ce9178' }, + { token: 'delimiter.bracket', foreground: 'd7ba7d' }, + { token: 'jsx.attribute.value', foreground: 'ce9178' }, + { token: 'tag.name', foreground: '569cd6' }, + ], + colors: { 'scrollbar.shadow': '#222222', 'diffEditor.insertedTextBackground': '#1C4D2D', @@ -76,6 +101,7 @@ export function CodeView({ 'editor.lineHighlightBorder': '#6a57fd', }, }); + monaco.editor.setTheme('bit'); }; @@ -131,5 +157,5 @@ function EmptyCodeView() { } export function CodeViewLoader({ className, ...rest }: React.HTMLAttributes) { - return ; + return ; } diff --git a/components/ui/code-view/monaco-language-init.ts b/components/ui/code-view/monaco-language-init.ts new file mode 100644 index 000000000000..b7a2e87561b9 --- /dev/null +++ b/components/ui/code-view/monaco-language-init.ts @@ -0,0 +1,116 @@ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { Monaco } from '@monaco-editor/react'; + +export const richLanguageConfiguration: monaco.languages.LanguageConfiguration = { + wordPattern: /(-?\d*\.\d\w*)|([^`~!@#%^&*()-=+[\]{}\\|;:'",.<>/?\s]+)/g, + comments: { + lineComment: '//', + blockComment: ['/*', '*/'], + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ], + __electricCharacterSupport: { + docComment: { open: '/**', close: ' */' }, + }, + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"', notIn: ['string'] }, + { open: "'", close: "'", notIn: ['string', 'comment'] }, + { open: '`', close: '`' }, + ], +}; + +export const customTokenizer: monaco.languages.IMonarchLanguage = { + tokenizer: { + root: [ + [/<(?=[A-Za-z])/, { token: 'delimiter.angle', next: '@tag' }], + [/<\/(?=[A-Za-z])/, { token: 'delimiter.angle', next: '@closingTag' }], + ], + tag: [ + [/([A-Za-z_$][A-Za-z$]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' } }], + [/\/>/, { token: 'delimiter.angle', next: '@pop' }], + [/>/, { token: 'delimiter.angle', next: '@pop' }], + [/(\w+)(?=\s*=)/, 'attribute.key'], + [/=/, 'delimiter'], + [/"/, { token: 'attribute.value', next: '@doubleString' }], + [/'/, { token: 'attribute.value', next: '@singleString' }], + [/{/, { token: 'delimiter.bracket', next: '@jsxAttributeValue' }], + [/[ \t\r\n]+/, ''], + ], + closingTag: [ + [/([A-Za-z_$][A-Za-z$]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' }, next: '@pop' }], + [/>/, { token: 'delimiter.angle', next: '@pop' }], + ], + doubleString: [ + [/[^"]+/, 'attribute.value'], + [/"/, { token: 'attribute.value', next: '@pop' }], + ], + singleString: [ + [/[^']+/, 'attribute.value'], + [/'/, { token: 'attribute.value', next: '@pop' }], + ], + jsxAttributeValue: [ + [/{/, { token: 'delimiter.bracket', next: '@nestedJsxAttributeValue' }], + [/}/, { token: 'delimiter.bracket', next: '@pop' }], + [/[^{}]+/, 'attribute.key'], + ], + nestedJsxAttributeValue: [ + [/{/, { token: 'delimiter.bracket', next: '@nestedJsxAttributeValue' }], + [/}/, { token: 'delimiter.bracket', next: '@pop' }], + [/[^{}]+/, 'attribute.key'], + ], + }, +}; + +/** + * Modifies the tokenizer of a given language in a Monaco Editor instance with custom tokens. + * The function operates in a non-destructive manner, ensuring the base object reference remains unaffected. + * + * How it works: + * 1. Retrieves all the available language configurations from the Monaco Editor instance. + * 2. Filters out the desired language (either 'typescript' or 'javascript'). + * 3. Executes the loader method, which is available for all registered languages, and retrieves the language model containing the tokenizer data. + * 4. Modifies the tokenizer data with the custom tokens defined in `customTokenizer`. + * + * Note: The modifications are applied by prepending custom tokens to the existing tokenizer categories. If a category from `customTokenizer` does not exist in the language model, it is created. + * + * @param monacoEditor - The Monaco Editor instance from which to retrieve the language configurations. + * @param language - The language to modify. Must be either 'typescript' or 'javascript'. + */ + +export async function setupLanguage(monacoEditor: Monaco, language: 'typescript' | 'javascript') { + const allLangs = monacoEditor.languages.getLanguages() as any; + + const { language: languageModel } = await allLangs.find(({ id }) => id === language).loader(); + + for (const key in customTokenizer) { + const value = customTokenizer[key]; + if (key === 'tokenizer') { + for (const category in value) { + const tokenDefs = value[category]; + // eslint-disable-next-line no-prototype-builtins + if (!languageModel.tokenizer.hasOwnProperty(category)) { + languageModel.tokenizer[category] = []; + } + if (Array.isArray(tokenDefs)) { + // eslint-disable-next-line prefer-spread + languageModel.tokenizer[category].unshift.apply(languageModel.tokenizer[category], tokenDefs); + } + } + } else if (Array.isArray(value)) { + // eslint-disable-next-line no-prototype-builtins + if (!languageModel.hasOwnProperty(key)) { + languageModel[key] = []; + } + // eslint-disable-next-line prefer-spread + languageModel[key].unshift.apply(languageModel[key], value); + } + } +} From e60f536e50f9bff6e29bc8624035bb3e7eac5631 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 19 May 2023 09:35:56 -0400 Subject: [PATCH 05/12] fix tokenizer --- components/ui/code-view/code-view.tsx | 1 - components/ui/code-view/monaco-language-init.ts | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx index 1ca9745105a3..0ebc81893aff 100644 --- a/components/ui/code-view/code-view.tsx +++ b/components/ui/code-view/code-view.tsx @@ -89,7 +89,6 @@ export function CodeView({ { token: 'attribute.value', foreground: 'ce9178' }, { token: 'delimiter.bracket', foreground: 'd7ba7d' }, { token: 'jsx.attribute.value', foreground: 'ce9178' }, - { token: 'tag.name', foreground: '569cd6' }, ], colors: { diff --git a/components/ui/code-view/monaco-language-init.ts b/components/ui/code-view/monaco-language-init.ts index b7a2e87561b9..43da0dd973f1 100644 --- a/components/ui/code-view/monaco-language-init.ts +++ b/components/ui/code-view/monaco-language-init.ts @@ -34,15 +34,18 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { [/<\/(?=[A-Za-z])/, { token: 'delimiter.angle', next: '@closingTag' }], ], tag: [ - [/([A-Za-z_$][A-Za-z$]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' } }], + [/\s+/, ''], + [ + /([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*[/>]|[\s\n]*)/, + { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' } }, + ], + [/([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*=)/, 'attribute.key'], // 2 [/\/>/, { token: 'delimiter.angle', next: '@pop' }], [/>/, { token: 'delimiter.angle', next: '@pop' }], - [/(\w+)(?=\s*=)/, 'attribute.key'], [/=/, 'delimiter'], [/"/, { token: 'attribute.value', next: '@doubleString' }], [/'/, { token: 'attribute.value', next: '@singleString' }], [/{/, { token: 'delimiter.bracket', next: '@jsxAttributeValue' }], - [/[ \t\r\n]+/, ''], ], closingTag: [ [/([A-Za-z_$][A-Za-z$]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' }, next: '@pop' }], @@ -59,6 +62,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { jsxAttributeValue: [ [/{/, { token: 'delimiter.bracket', next: '@nestedJsxAttributeValue' }], [/}/, { token: 'delimiter.bracket', next: '@pop' }], + [/(\w+)(?=[\s\n]*=)/, 'attribute.key'], [/[^{}]+/, 'attribute.key'], ], nestedJsxAttributeValue: [ From 9775be001090ed8fc89bcc40631f8585ad7c203e Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Tue, 23 May 2023 13:18:04 -0400 Subject: [PATCH 06/12] fix infinite loop with resize observer --- components/ui/code-view/code-view.tsx | 26 +++++++------------ .../ui/code-view/monaco-language-init.ts | 3 ++- .../code-compare-view/code-compare-view.tsx | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx index 0ebc81893aff..dbbe4342d7dc 100644 --- a/components/ui/code-view/code-view.tsx +++ b/components/ui/code-view/code-view.tsx @@ -49,6 +49,15 @@ export function CodeView({ !!currentFileContent ); + const loading = loadingFromProps || loadingFileContent; + const fileContent = currentFileContent || downloadedFileContent; + const title = useMemo(() => currentFile?.split('/').pop(), [currentFile]); + const language = useMemo(() => { + if (!currentFile) return languageOverrides.ts; + const fileEnding = currentFile?.split('.').pop(); + return languageOverrides[fileEnding || ''] || fileEnding; + }, [currentFile]); + const handleEditorDidMount: OnMount = (editor, monaco) => { /** * disable syntax check @@ -56,11 +65,6 @@ export function CodeView({ */ monacoRef.current = { monaco, editor }; - // monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ - // noSemanticValidation: true, - // noSyntaxValidation: true, - // }); - monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ target: monaco.languages.typescript.ScriptTarget.Latest, allowNonTsExtensions: true, @@ -74,7 +78,7 @@ export function CodeView({ esModuleInterop: true, }); - setupLanguage(monaco, 'typescript').catch(() => {}); + setupLanguage(monaco, language).catch(() => {}); monaco.editor.defineTheme('bit', { base: 'vs-dark', @@ -103,16 +107,6 @@ export function CodeView({ monaco.editor.setTheme('bit'); }; - - const loading = loadingFromProps || loadingFileContent; - const fileContent = currentFileContent || downloadedFileContent; - const title = useMemo(() => currentFile?.split('/').pop(), [currentFile]); - const language = useMemo(() => { - if (!currentFile) return languageOverrides.ts; - const fileEnding = currentFile?.split('.').pop(); - return languageOverrides[fileEnding || ''] || fileEnding; - }, [currentFile]); - const codeEditor = useMemo( () => ( ]|[\s\n]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' } }, ], - [/([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*=)/, 'attribute.key'], // 2 + [/([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*=)/, 'attribute.key'], [/\/>/, { token: 'delimiter.angle', next: '@pop' }], [/>/, { token: 'delimiter.angle', next: '@pop' }], [/=/, 'delimiter'], @@ -62,6 +62,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { jsxAttributeValue: [ [/{/, { token: 'delimiter.bracket', next: '@nestedJsxAttributeValue' }], [/}/, { token: 'delimiter.bracket', next: '@pop' }], + [/<(?=[A-Za-z])/, { token: 'delimiter.angle', next: '@tag' }], [/(\w+)(?=[\s\n]*=)/, 'attribute.key'], [/[^{}]+/, 'attribute.key'], ], diff --git a/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx b/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx index 83049aea450c..5dff2d099599 100644 --- a/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx +++ b/scopes/code/ui/code-compare/code-compare-view/code-compare-view.tsx @@ -242,7 +242,7 @@ export function CodeCompareView({ if (containerElement) { resizeObserver = new ResizeObserver(() => { - updateEditorHeight(); + setTimeout(() => updateEditorHeight()); }); resizeObserver.observe(containerElement); } From 8e54c19ab0bf75cc39a6f803586e4f305e6dc6f4 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Tue, 23 May 2023 14:28:23 -0400 Subject: [PATCH 07/12] fix fullscreen lane compare page --- .../ui/compare/lane-compare-page/lane-compare-page.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/ui/compare/lane-compare-page/lane-compare-page.module.scss b/components/ui/compare/lane-compare-page/lane-compare-page.module.scss index 9ebd3df4bebc..cc4146630559 100644 --- a/components/ui/compare/lane-compare-page/lane-compare-page.module.scss +++ b/components/ui/compare/lane-compare-page/lane-compare-page.module.scss @@ -4,6 +4,7 @@ height: 100%; width: 100%; } + .top { display: flex; padding: 24px; @@ -42,6 +43,7 @@ .bottom { display: flex; overflow: auto; + height: 100%; } .compareLane { From a21c61f3a3e8cdd8c1a2658e1b7f81593834884b Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Wed, 24 May 2023 15:10:28 -0400 Subject: [PATCH 08/12] tokenizer fixes --- .../ui/code-view/monaco-language-init.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/components/ui/code-view/monaco-language-init.ts b/components/ui/code-view/monaco-language-init.ts index 9ebfc185a2ab..42782858d081 100644 --- a/components/ui/code-view/monaco-language-init.ts +++ b/components/ui/code-view/monaco-language-init.ts @@ -36,10 +36,16 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { tag: [ [/\s+/, ''], [ - /([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*[/>]|[\s\n]*)/, - { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' } }, + /([A-Za-z_$][A-Za-z0-9_$]*)(?=\s*[\s\n]*[/>]|[\s\n]*=|[\s\n]*)/, + { + cases: { + '[A-Z$][\\w$]*(?!\\s*=)': 'tag.custom', + '[a-z$][\\w$]*(?!\\s*=)': 'tag.dom', + '@default': '@attribute', + }, + }, ], - [/([A-Za-z_$][A-Za-z$]*)(?=[\s\n]*=)/, 'attribute.key'], + [/([A-Za-z_$][\w$-]*|[\w$-]+(?=\s*=))/, 'attribute.key'], [/\/>/, { token: 'delimiter.angle', next: '@pop' }], [/>/, { token: 'delimiter.angle', next: '@pop' }], [/=/, 'delimiter'], @@ -47,8 +53,23 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { [/'/, { token: 'attribute.value', next: '@singleString' }], [/{/, { token: 'delimiter.bracket', next: '@jsxAttributeValue' }], ], + attribute: [ + [/([A-Za-z_$][\w$-]*)(?=[\s\n]*=)/, 'attribute.key'], + [/\/>/, { token: 'delimiter.angle', next: '@pop' }], + [/>/, { token: 'delimiter.angle', next: '@pop' }], + [/=/, 'delimiter'], + [/"/, { token: 'attribute.value', next: '@doubleString' }], + [/'/, { token: 'attribute.value', next: '@singleString' }], + [/{/, { token: 'delimiter.bracket', next: '@jsxAttributeValue' }], + [/\s+/, ''], + [/[^=\s>]+/, 'attribute.value'], + ], closingTag: [ - [/([A-Za-z_$][A-Za-z$]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' }, next: '@pop' }], + [/\s+/, ''], + [ + /([A-Za-z_$][A-Za-z0-9_$]*)(?=[\s\n]*[/>]|[\s\n]*=|[\s\n]*)/, + { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' }, next: '@pop' }, + ], [/>/, { token: 'delimiter.angle', next: '@pop' }], ], doubleString: [ From 740a61383e538cfad4a936e47645d6def2ccfdc2 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Thu, 25 May 2023 16:21:32 -0400 Subject: [PATCH 09/12] fix API Reference code snippet dynamic height calc bug + tokenizer enhancements --- .../ui/code-view/monaco-language-init.ts | 9 +- .../api-node-details/api-node-details.tsx | 253 +++++++++++++----- .../code-editor-options.ts | 5 +- scopes/code/ui/code-editor/code-editor.tsx | 88 ++++-- 4 files changed, 259 insertions(+), 96 deletions(-) diff --git a/components/ui/code-view/monaco-language-init.ts b/components/ui/code-view/monaco-language-init.ts index 42782858d081..92e5d2152c6c 100644 --- a/components/ui/code-view/monaco-language-init.ts +++ b/components/ui/code-view/monaco-language-init.ts @@ -36,7 +36,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { tag: [ [/\s+/, ''], [ - /([A-Za-z_$][A-Za-z0-9_$]*)(?=\s*[\s\n]*[/>]|[\s\n]*=|[\s\n]*)/, + /([A-Za-z_$][A-Za-z0-9_.$]*)(?=\s+[\s\n]*[/>]|[\s\n]*=|[\s\n]*)/, { cases: { '[A-Z$][\\w$]*(?!\\s*=)': 'tag.custom', @@ -45,7 +45,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { }, }, ], - [/([A-Za-z_$][\w$-]*|[\w$-]+(?=\s*=))/, 'attribute.key'], + [/([A-Za-z_$][\w$-]*)(?=\s*=)/, 'attribute.key'], [/\/>/, { token: 'delimiter.angle', next: '@pop' }], [/>/, { token: 'delimiter.angle', next: '@pop' }], [/=/, 'delimiter'], @@ -54,7 +54,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { [/{/, { token: 'delimiter.bracket', next: '@jsxAttributeValue' }], ], attribute: [ - [/([A-Za-z_$][\w$-]*)(?=[\s\n]*=)/, 'attribute.key'], + [/([A-Za-z_$][\w$-]*)(?=\s*=)/, 'attribute.key'], [/\/>/, { token: 'delimiter.angle', next: '@pop' }], [/>/, { token: 'delimiter.angle', next: '@pop' }], [/=/, 'delimiter'], @@ -67,7 +67,7 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { closingTag: [ [/\s+/, ''], [ - /([A-Za-z_$][A-Za-z0-9_$]*)(?=[\s\n]*[/>]|[\s\n]*=|[\s\n]*)/, + /([A-Za-z_$][A-Za-z0-9_.$]*)(?=[\s\n]*[/>]|[\s\n]*)/, { cases: { '[A-Z$][\\w$]*': 'tag.custom', '@default': 'tag.dom' }, next: '@pop' }, ], [/>/, { token: 'delimiter.angle', next: '@pop' }], @@ -84,7 +84,6 @@ export const customTokenizer: monaco.languages.IMonarchLanguage = { [/{/, { token: 'delimiter.bracket', next: '@nestedJsxAttributeValue' }], [/}/, { token: 'delimiter.bracket', next: '@pop' }], [/<(?=[A-Za-z])/, { token: 'delimiter.angle', next: '@tag' }], - [/(\w+)(?=[\s\n]*=)/, 'attribute.key'], [/[^{}]+/, 'attribute.key'], ], nestedJsxAttributeValue: [ diff --git a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx index 039020b8f372..ac3a4e94bf35 100644 --- a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx +++ b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { H6 } from '@teambit/documenter.ui.heading'; -import Editor from '@monaco-editor/react'; +import { CodeEditor } from '@teambit/code.ui.code-editor'; import { useLocation } from '@teambit/base-react.navigation.link'; import { defaultCodeEditorOptions } from '@teambit/api-reference.utils.code-editor-options'; import classnames from 'classnames'; @@ -10,6 +10,8 @@ import { APIRefQueryParams } from '@teambit/api-reference.hooks.use-api-ref-url' import { useNavigate } from 'react-router-dom'; import { APINode } from '@teambit/api-reference.models.api-reference-model'; import { SchemaNodesIndex } from '@teambit/api-reference.renderers.schema-nodes-index'; +import { OnMount, Monaco } from '@monaco-editor/react'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import styles from './api-node-details.module.scss'; @@ -42,33 +44,41 @@ export function APINodeDetails({ const routerLocation = useLocation(); const query = useQuery(); const navigate = useNavigate(); - const editorRef = useRef(); - const monacoRef = useRef(); + + const signatureEditorRef = useRef(); + const signatureMonacoRef = useRef(); + + const exampleEditorRef = useRef(); + const exampleMonacoRef = useRef(); + const routeToAPICmdId = useRef(null); const apiUrlToRoute = useRef(null); + const hoverProviderDispose = useRef(); + const rootRef = useRef() as React.MutableRefObject; const apiRef = useRef(null); - const currentQueryParams = query.toString(); - const [containerSize, setContainerSize] = useState<{ width?: number; height?: number }>({ + + const signatureContainerRef = useRef(null); + const exampleContainerRef = useRef(null); + + const [signatureHeight, setSignatureHeight] = useState(); + const [exampleHeight, setExampleHeight] = useState(); + + const [containerSize] = useState<{ width?: number; height?: number }>({ width: undefined, height: undefined, }); + + const currentQueryParams = query.toString(); + const signatureHeightStyle = (!!signatureHeight && `calc(${signatureHeight} + 16px)`) || '250px'; + const exampleHeightStyle = (!!exampleHeight && `calc(${exampleHeight} + 16px)`) || '250px'; + const indexHidden = (containerSize.width ?? 0) < INDEX_THRESHOLD_WIDTH; const example = (doc?.tags || []).find((tag) => tag.tagName === 'example'); const comment = doc?.comment; const signature = displaySignature || defaultSignature; - /** - * @HACK - * Make Monaco responsive - * default line height: 18px; - * totalHeight: (no of lines * default line height) - */ - const exampleHeight = (example?.comment?.split('\n').length || 0) * 18; - const defaultSignatureHeight = 36 + ((signature?.split('\n').length || 0) - 1) * 18; - - const [signatureHeight, setSignatureHeight] = useState(defaultSignatureHeight); const [isMounted, setIsMounted] = useState(false); const getAPINodeUrl = useCallback((queryParams: APIRefQueryParams) => { @@ -97,54 +107,158 @@ export function APINodeDetails({ useEffect(() => { if (isMounted && signature) { - monacoRef.current.languages.typescript.typescriptDefaults.setCompilerOptions({ - jsx: monacoRef.current.languages.typescript.JsxEmit.Preserve, - target: monacoRef.current.languages.typescript.ScriptTarget.ES2020, + signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setCompilerOptions({ + jsx: signatureMonacoRef.current.languages.typescript.JsxEmit.Preserve, + target: signatureMonacoRef.current.languages.typescript.ScriptTarget.ES2020, esModuleInterop: true, }); - ``; - monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true, noSyntaxValidation: true, }); - const container = editorRef.current.getDomNode(); - editorRef.current.onDidContentSizeChange(({ contentHeight }) => { - if (container && isMounted && signature) { - const updatedHeight = Math.min(200, contentHeight + 18); - setSignatureHeight(updatedHeight); - } - }); - routeToAPICmdId.current = editorRef.current.addCommand(0, () => { - apiUrlToRoute.current && navigate(apiUrlToRoute.current); - }); + + routeToAPICmdId.current = + signatureEditorRef.current?.addCommand(0, () => { + apiUrlToRoute.current && navigate(apiUrlToRoute.current); + }) ?? null; + if (!hoverProviderDispose.current) { - hoverProviderDispose.current = monacoRef.current.languages.registerHoverProvider('typescript', { + hoverProviderDispose.current = signatureMonacoRef.current?.languages.registerHoverProvider('typescript', { provideHover: hoverProvider, }); } } }, [isMounted]); - const handleSize = useCallback(() => { - setContainerSize({ - width: rootRef.current.offsetWidth, - height: rootRef.current.offsetHeight, + const getDisplayedLineCount = (editorInstance, containerWidth) => { + if (!signatureMonacoRef.current) return 0; + + const model = editorInstance.getModel(); + + if (!model) { + return 0; + } + + const lineCount = model.getLineCount(); + + let displayedLines = 0; + + const lineWidth = editorInstance.getOption(signatureMonacoRef.current.editor.EditorOption.wordWrapColumn); + const fontWidthApproximation = 8; + + for (let lineNumber = 1; lineNumber <= lineCount; lineNumber += 1) { + const line = model.getLineContent(lineNumber); + const length = line.length || 1; + const lineFitsContainer = length * fontWidthApproximation <= containerWidth; + const wrappedLineCount = (lineFitsContainer ? 1 : Math.ceil(length / lineWidth)) || 1; + displayedLines += wrappedLineCount; + } + + return displayedLines; + }; + + const updateEditorHeight = + ( + setHeight: React.Dispatch>, + editorRef: React.MutableRefObject + ) => + () => { + if (!signatureMonacoRef.current) return undefined; + + const editor = editorRef.current; + + if (!editor) { + return undefined; + } + + const lineHeight = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineHeight); + + const paddingTop = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.top || 0; + const paddingBottom = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.bottom || 0; + const glyphMargin = editor.getOption(signatureMonacoRef.current.editor.EditorOption.glyphMargin); + const lineNumbers = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineNumbers); + + const glyphMarginHeight = glyphMargin ? lineHeight : 0; + const lineNumbersHeight = lineNumbers.renderType !== 0 ? lineHeight : 0; + + const containerWidth = editor.getLayoutInfo().contentWidth; + const displayedLines = getDisplayedLineCount(editor, containerWidth); + + const contentHeight = + displayedLines * lineHeight + paddingTop + paddingBottom + glyphMarginHeight + lineNumbersHeight; + + const domNode = editor.getDomNode()?.parentElement; + + if (!domNode) { + return undefined; + } + + domNode.style.height = `${contentHeight}px`; + signatureEditorRef.current?.layout(); + setHeight(() => `${contentHeight}px`); + return undefined; + }; + + const handleEditorDidMount: ( + monacoRef: React.MutableRefObject, + editorRef: React.MutableRefObject, + containerRef: React.MutableRefObject, + setHeight: React.Dispatch> + ) => OnMount = (monacoRef, editorRef, containerRef, setHeight) => (editor, _monaco) => { + /** + * disable syntax check + * ts cant validate all types because imported files aren't available to the editor + */ + monacoRef.current = _monaco; + editorRef.current = editor; + + monacoRef.current.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ + noSemanticValidation: true, + noSyntaxValidation: true, }); - }, []); + + monaco.editor.defineTheme('bit', { + base: 'vs-dark', + inherit: true, + rules: [], + colors: { + 'scrollbar.shadow': '#222222', + 'diffEditor.insertedTextBackground': '#1C4D2D', + 'diffEditor.removedTextBackground': '#761E24', + 'editor.selectionBackground': '#5A5A5A', + 'editor.overviewRulerBorder': '#6a57fd', + 'editor.lineHighlightBorder': '#6a57fd', + }, + }); + monaco.editor.setTheme('bit'); + editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); + editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); + const containerElement = containerRef.current; + let resizeObserver: ResizeObserver | undefined; + + if (containerElement) { + resizeObserver = new ResizeObserver(() => { + setTimeout(() => updateEditorHeight(setHeight, editorRef)); + }); + resizeObserver.observe(containerElement); + } + + return () => containerElement && resizeObserver?.unobserve(containerElement); + }; useEffect(() => { - if (window) window.addEventListener('resize', handleSize); - // Call handler right away so state gets updated with initial container size - handleSize(); + updateEditorHeight(setSignatureHeight, signatureEditorRef)(); + updateEditorHeight(setExampleHeight, exampleEditorRef)(); + return () => { hoverProviderDispose.current?.dispose(); - if (window) window.removeEventListener('resize', handleSize); setIsMounted(false); }; }, []); - useEffect(() => { - handleSize(); + React.useLayoutEffect(() => { + updateEditorHeight(setSignatureHeight, signatureEditorRef)(); + updateEditorHeight(setExampleHeight, exampleEditorRef)(); }, [rootRef?.current?.offsetHeight, rootRef?.current?.offsetWidth]); return ( @@ -164,38 +278,53 @@ export function APINodeDetails({
- { - monacoRef.current = monaco; - }} - onMount={(editor) => { - editorRef.current = editor; - const signatureContent = editorRef.current.getValue(); - const updatedSignatureHeight = 36 + ((signatureContent?.split('\n').length || 0) - 1) * 18; - setIsMounted(true); - setSignatureHeight(updatedSignatureHeight); + handleEditorBeforeMount={(_monaco) => { + signatureMonacoRef.current = _monaco; }} - theme={'vs-dark'} + handleEditorDidMount={handleEditorDidMount( + signatureMonacoRef, + signatureEditorRef, + signatureContainerRef, + setSignatureHeight + )} />
)} {example && example.comment && (
Example
-
- +
diff --git a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts index 72f32f0f465a..4ea7666d42f5 100644 --- a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts +++ b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts @@ -21,6 +21,7 @@ export const defaultCodeEditorOptions: monaco.editor.IStandaloneEditorConstructi enabled: false, }, renderLineHighlight: 'none', - lineHeight: 18, - padding: { top: 8 }, + lineHeight: 20, + padding: { top: 8, bottom: 8 }, + cursorBlinking: 'smooth', }; diff --git a/scopes/code/ui/code-editor/code-editor.tsx b/scopes/code/ui/code-editor/code-editor.tsx index 6ba07b82179f..02fe06f29dbf 100644 --- a/scopes/code/ui/code-editor/code-editor.tsx +++ b/scopes/code/ui/code-editor/code-editor.tsx @@ -1,43 +1,77 @@ import React from 'react'; -import Editor, { OnMount } from '@monaco-editor/react'; +import classnames from 'classnames'; +import Editor, { OnMount, BeforeMount } from '@monaco-editor/react'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { darkMode } from '@teambit/base-ui.theme.dark-theme'; export type CodeEditorProps = { - language: string; - handleEditorDidMount: OnMount; - Loader: React.ReactNode; - fileContent?: string; - originalFileContent?: string; filePath?: string; + fileContent?: string; + language?: string; + handleEditorDidMount?: OnMount; + handleEditorBeforeMount?: BeforeMount; + Loader?: React.ReactNode; + options?: monaco.editor.IStandaloneEditorConstructionOptions; + className?: string; + height?: string; +}; + +export const DEFAULT_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = { + readOnly: true, + minimap: { enabled: false }, + scrollbar: { alwaysConsumeMouseWheel: true, vertical: 'auto' }, + scrollBeyondLastLine: false, + folding: false, + overviewRulerLanes: 0, + overviewRulerBorder: false, + wordWrap: 'off', + wrappingStrategy: undefined, + fixedOverflowWidgets: true, + renderLineHighlight: 'none', + lineHeight: 20, + padding: { top: 8 }, + hover: { enabled: false }, + cursorBlinking: 'smooth', }; -export function CodeEditor({ fileContent, filePath, language, handleEditorDidMount, Loader }: CodeEditorProps) { +// a translation list of specific monaco languages that are not the same as their file ending. +const languageOverrides = { + ts: 'typescript', + tsx: 'typescript', + js: 'javascript', + jsx: 'javascript', + mdx: 'markdown', + md: 'markdown', +}; + +export function CodeEditor({ + fileContent, + filePath, + language, + handleEditorBeforeMount, + handleEditorDidMount, + Loader, + options, + className, + height, +}: CodeEditorProps) { + const defaultLang = React.useMemo(() => { + if (!filePath) return languageOverrides.ts; + const fileEnding = filePath?.split('.').pop(); + return languageOverrides[fileEnding || ''] || fileEnding; + }, [filePath]); + return ( ); From 8bf7e24f3c32b3b7d29a76e9c1ecbbe1d9a33bee Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 26 May 2023 09:44:14 -0400 Subject: [PATCH 10/12] fix origin in api ref url + style fixes --- .../function-node-summary.module.scss | 2 ++ .../renderers/type-ref/type-ref.renderer.tsx | 6 +++--- .../api-reference-page/api-reference-page.tsx | 16 +++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss index bdbb6843e151..243c235480f9 100644 --- a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss +++ b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss @@ -23,6 +23,7 @@ .row { grid-template-columns: 1fr 1fr 2fr; + &.isHovering { background-color: #f4f5f6; cursor: pointer; @@ -44,6 +45,7 @@ .signatureDetails { background-color: var(--bit-bg-heavy, #f4f5f6); + margin-left: 8px; } .returnContainer, diff --git a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx index cd958d4d3a57..c4636780560b 100644 --- a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx +++ b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx @@ -64,7 +64,7 @@ function TypeRefComponent(props: APINodeRenderProps) { if (typeArgRenderer) { return ( - + + {typeArg.toString()} {(typeArgs?.length ?? 0) > 1 && index !== (typeArgs?.length ?? 0) - 1 ? ', ' : null} @@ -148,7 +148,7 @@ function getExportedTypeUrlFromAnotherComp({ componentId: ComponentID; selectedAPI: string; }) { - const componentUrl = ComponentUrl.toUrl(componentId); + const componentUrl = ComponentUrl.toUrl(componentId, { useLocationOrigin: true }); const [componentIdUrl, versionQuery] = componentUrl.split('?'); const exportedTypeUrl = `${componentIdUrl}/~api-reference?selectedAPI=${encodeURIComponent( diff --git a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx index 4b85b8f44be7..52c25494a3a1 100644 --- a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx +++ b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { ComponentContext } from '@teambit/component'; import { H1 } from '@teambit/documenter.ui.heading'; import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile'; -import { useLocation, Link } from '@teambit/base-react.navigation.link'; +import { Link } from '@teambit/base-react.navigation.link'; import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser'; import { HoverSplitter } from '@teambit/base-ui.surfaces.split-pane.hover-splitter'; @@ -35,7 +35,9 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { const sidebarOpenness = isSidebarOpen ? Layout.row : Layout.left; const selectedAPIFromUrl = useAPIRefParam('selectedAPI'); - + React.useEffect(() => { + window.scrollTo(0, 0); + }, [selectedAPIFromUrl]); const apiNodes = (apiModel && flatten(Array.from(apiModel.apiByType.values())).sort(sortAPINodes)) || []; const isEmpty = apiNodes.length === 0; @@ -58,7 +60,7 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { (selectedAPINode && `${selectedAPINode?.renderer?.nodeType}/${selectedAPINode?.api.name}`) || apiTree[0]; const SelectedAPIComponent = selectedAPINode && selectedAPINode.renderer.Component; - const location = useLocation(); + // const location = useLocation(); const query = useQuery(); if (loading) { @@ -72,18 +74,14 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { if (!apiModel || isEmpty) { return ; } - const icon = selectedAPINode.renderer.icon; const name = selectedAPINode.api.name; const componentVersionFromUrl = query.get('version'); const filePath = selectedAPINode.api.location.filePath; - const pathname = - location?.pathname && window?.location?.hostname?.startsWith('localhost') - ? location?.pathname - : `${ComponentUrl.toUrl(component.id, { includeVersion: false })}/`; + const pathname = ComponentUrl.toUrl(component.id, { includeVersion: false, useLocationOrigin: true }); const componentUrlWithoutVersion = pathname?.split('~')[0]; - const locationUrl = `${componentUrlWithoutVersion}~code/${filePath}${ + const locationUrl = `${componentUrlWithoutVersion}/~code/${filePath}${ componentVersionFromUrl ? `?version=${componentVersionFromUrl}` : '' }`; From 69a4e8b1f4accc057ef318a31fe45f36ceb6da87 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Mon, 29 May 2023 14:35:02 -0400 Subject: [PATCH 11/12] remove api ref fixes --- .../api-node-details/api-node-details.tsx | 253 +++++------------- .../function-node-summary.module.scss | 2 - .../renderers/type-ref/type-ref.renderer.tsx | 6 +- .../api-reference-page/api-reference-page.tsx | 16 +- .../code-editor-options.ts | 5 +- scopes/code/ui/code-editor/code-editor.tsx | 25 +- 6 files changed, 90 insertions(+), 217 deletions(-) diff --git a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx index ac3a4e94bf35..039020b8f372 100644 --- a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx +++ b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { H6 } from '@teambit/documenter.ui.heading'; -import { CodeEditor } from '@teambit/code.ui.code-editor'; +import Editor from '@monaco-editor/react'; import { useLocation } from '@teambit/base-react.navigation.link'; import { defaultCodeEditorOptions } from '@teambit/api-reference.utils.code-editor-options'; import classnames from 'classnames'; @@ -10,8 +10,6 @@ import { APIRefQueryParams } from '@teambit/api-reference.hooks.use-api-ref-url' import { useNavigate } from 'react-router-dom'; import { APINode } from '@teambit/api-reference.models.api-reference-model'; import { SchemaNodesIndex } from '@teambit/api-reference.renderers.schema-nodes-index'; -import { OnMount, Monaco } from '@monaco-editor/react'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import styles from './api-node-details.module.scss'; @@ -44,41 +42,33 @@ export function APINodeDetails({ const routerLocation = useLocation(); const query = useQuery(); const navigate = useNavigate(); - - const signatureEditorRef = useRef(); - const signatureMonacoRef = useRef(); - - const exampleEditorRef = useRef(); - const exampleMonacoRef = useRef(); - + const editorRef = useRef(); + const monacoRef = useRef(); const routeToAPICmdId = useRef(null); const apiUrlToRoute = useRef(null); - const hoverProviderDispose = useRef(); - const rootRef = useRef() as React.MutableRefObject; const apiRef = useRef(null); - - const signatureContainerRef = useRef(null); - const exampleContainerRef = useRef(null); - - const [signatureHeight, setSignatureHeight] = useState(); - const [exampleHeight, setExampleHeight] = useState(); - - const [containerSize] = useState<{ width?: number; height?: number }>({ + const currentQueryParams = query.toString(); + const [containerSize, setContainerSize] = useState<{ width?: number; height?: number }>({ width: undefined, height: undefined, }); - - const currentQueryParams = query.toString(); - const signatureHeightStyle = (!!signatureHeight && `calc(${signatureHeight} + 16px)`) || '250px'; - const exampleHeightStyle = (!!exampleHeight && `calc(${exampleHeight} + 16px)`) || '250px'; - const indexHidden = (containerSize.width ?? 0) < INDEX_THRESHOLD_WIDTH; const example = (doc?.tags || []).find((tag) => tag.tagName === 'example'); const comment = doc?.comment; const signature = displaySignature || defaultSignature; + /** + * @HACK + * Make Monaco responsive + * default line height: 18px; + * totalHeight: (no of lines * default line height) + */ + const exampleHeight = (example?.comment?.split('\n').length || 0) * 18; + const defaultSignatureHeight = 36 + ((signature?.split('\n').length || 0) - 1) * 18; + + const [signatureHeight, setSignatureHeight] = useState(defaultSignatureHeight); const [isMounted, setIsMounted] = useState(false); const getAPINodeUrl = useCallback((queryParams: APIRefQueryParams) => { @@ -107,158 +97,54 @@ export function APINodeDetails({ useEffect(() => { if (isMounted && signature) { - signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setCompilerOptions({ - jsx: signatureMonacoRef.current.languages.typescript.JsxEmit.Preserve, - target: signatureMonacoRef.current.languages.typescript.ScriptTarget.ES2020, + monacoRef.current.languages.typescript.typescriptDefaults.setCompilerOptions({ + jsx: monacoRef.current.languages.typescript.JsxEmit.Preserve, + target: monacoRef.current.languages.typescript.ScriptTarget.ES2020, esModuleInterop: true, }); - signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + ``; + monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true, noSyntaxValidation: true, }); - - routeToAPICmdId.current = - signatureEditorRef.current?.addCommand(0, () => { - apiUrlToRoute.current && navigate(apiUrlToRoute.current); - }) ?? null; - + const container = editorRef.current.getDomNode(); + editorRef.current.onDidContentSizeChange(({ contentHeight }) => { + if (container && isMounted && signature) { + const updatedHeight = Math.min(200, contentHeight + 18); + setSignatureHeight(updatedHeight); + } + }); + routeToAPICmdId.current = editorRef.current.addCommand(0, () => { + apiUrlToRoute.current && navigate(apiUrlToRoute.current); + }); if (!hoverProviderDispose.current) { - hoverProviderDispose.current = signatureMonacoRef.current?.languages.registerHoverProvider('typescript', { + hoverProviderDispose.current = monacoRef.current.languages.registerHoverProvider('typescript', { provideHover: hoverProvider, }); } } }, [isMounted]); - const getDisplayedLineCount = (editorInstance, containerWidth) => { - if (!signatureMonacoRef.current) return 0; - - const model = editorInstance.getModel(); - - if (!model) { - return 0; - } - - const lineCount = model.getLineCount(); - - let displayedLines = 0; - - const lineWidth = editorInstance.getOption(signatureMonacoRef.current.editor.EditorOption.wordWrapColumn); - const fontWidthApproximation = 8; - - for (let lineNumber = 1; lineNumber <= lineCount; lineNumber += 1) { - const line = model.getLineContent(lineNumber); - const length = line.length || 1; - const lineFitsContainer = length * fontWidthApproximation <= containerWidth; - const wrappedLineCount = (lineFitsContainer ? 1 : Math.ceil(length / lineWidth)) || 1; - displayedLines += wrappedLineCount; - } - - return displayedLines; - }; - - const updateEditorHeight = - ( - setHeight: React.Dispatch>, - editorRef: React.MutableRefObject - ) => - () => { - if (!signatureMonacoRef.current) return undefined; - - const editor = editorRef.current; - - if (!editor) { - return undefined; - } - - const lineHeight = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineHeight); - - const paddingTop = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.top || 0; - const paddingBottom = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.bottom || 0; - const glyphMargin = editor.getOption(signatureMonacoRef.current.editor.EditorOption.glyphMargin); - const lineNumbers = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineNumbers); - - const glyphMarginHeight = glyphMargin ? lineHeight : 0; - const lineNumbersHeight = lineNumbers.renderType !== 0 ? lineHeight : 0; - - const containerWidth = editor.getLayoutInfo().contentWidth; - const displayedLines = getDisplayedLineCount(editor, containerWidth); - - const contentHeight = - displayedLines * lineHeight + paddingTop + paddingBottom + glyphMarginHeight + lineNumbersHeight; - - const domNode = editor.getDomNode()?.parentElement; - - if (!domNode) { - return undefined; - } - - domNode.style.height = `${contentHeight}px`; - signatureEditorRef.current?.layout(); - setHeight(() => `${contentHeight}px`); - return undefined; - }; - - const handleEditorDidMount: ( - monacoRef: React.MutableRefObject, - editorRef: React.MutableRefObject, - containerRef: React.MutableRefObject, - setHeight: React.Dispatch> - ) => OnMount = (monacoRef, editorRef, containerRef, setHeight) => (editor, _monaco) => { - /** - * disable syntax check - * ts cant validate all types because imported files aren't available to the editor - */ - monacoRef.current = _monaco; - editorRef.current = editor; - - monacoRef.current.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ - noSemanticValidation: true, - noSyntaxValidation: true, - }); - - monaco.editor.defineTheme('bit', { - base: 'vs-dark', - inherit: true, - rules: [], - colors: { - 'scrollbar.shadow': '#222222', - 'diffEditor.insertedTextBackground': '#1C4D2D', - 'diffEditor.removedTextBackground': '#761E24', - 'editor.selectionBackground': '#5A5A5A', - 'editor.overviewRulerBorder': '#6a57fd', - 'editor.lineHighlightBorder': '#6a57fd', - }, + const handleSize = useCallback(() => { + setContainerSize({ + width: rootRef.current.offsetWidth, + height: rootRef.current.offsetHeight, }); - monaco.editor.setTheme('bit'); - editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); - editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); - const containerElement = containerRef.current; - let resizeObserver: ResizeObserver | undefined; - - if (containerElement) { - resizeObserver = new ResizeObserver(() => { - setTimeout(() => updateEditorHeight(setHeight, editorRef)); - }); - resizeObserver.observe(containerElement); - } - - return () => containerElement && resizeObserver?.unobserve(containerElement); - }; + }, []); useEffect(() => { - updateEditorHeight(setSignatureHeight, signatureEditorRef)(); - updateEditorHeight(setExampleHeight, exampleEditorRef)(); - + if (window) window.addEventListener('resize', handleSize); + // Call handler right away so state gets updated with initial container size + handleSize(); return () => { hoverProviderDispose.current?.dispose(); + if (window) window.removeEventListener('resize', handleSize); setIsMounted(false); }; }, []); - React.useLayoutEffect(() => { - updateEditorHeight(setSignatureHeight, signatureEditorRef)(); - updateEditorHeight(setExampleHeight, exampleEditorRef)(); + useEffect(() => { + handleSize(); }, [rootRef?.current?.offsetHeight, rootRef?.current?.offsetWidth]); return ( @@ -278,53 +164,38 @@ export function APINodeDetails({
- { - signatureMonacoRef.current = _monaco; + beforeMount={(monaco) => { + monacoRef.current = monaco; + }} + onMount={(editor) => { + editorRef.current = editor; + const signatureContent = editorRef.current.getValue(); + const updatedSignatureHeight = 36 + ((signatureContent?.split('\n').length || 0) - 1) * 18; + setIsMounted(true); + setSignatureHeight(updatedSignatureHeight); }} - handleEditorDidMount={handleEditorDidMount( - signatureMonacoRef, - signatureEditorRef, - signatureContainerRef, - setSignatureHeight - )} + theme={'vs-dark'} />
)} {example && example.comment && (
Example
-
- +
diff --git a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss index 243c235480f9..bdbb6843e151 100644 --- a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss +++ b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss @@ -23,7 +23,6 @@ .row { grid-template-columns: 1fr 1fr 2fr; - &.isHovering { background-color: #f4f5f6; cursor: pointer; @@ -45,7 +44,6 @@ .signatureDetails { background-color: var(--bit-bg-heavy, #f4f5f6); - margin-left: 8px; } .returnContainer, diff --git a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx index c4636780560b..cd958d4d3a57 100644 --- a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx +++ b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx @@ -64,7 +64,7 @@ function TypeRefComponent(props: APINodeRenderProps) { if (typeArgRenderer) { return ( - + + {typeArg.toString()} {(typeArgs?.length ?? 0) > 1 && index !== (typeArgs?.length ?? 0) - 1 ? ', ' : null} @@ -148,7 +148,7 @@ function getExportedTypeUrlFromAnotherComp({ componentId: ComponentID; selectedAPI: string; }) { - const componentUrl = ComponentUrl.toUrl(componentId, { useLocationOrigin: true }); + const componentUrl = ComponentUrl.toUrl(componentId); const [componentIdUrl, versionQuery] = componentUrl.split('?'); const exportedTypeUrl = `${componentIdUrl}/~api-reference?selectedAPI=${encodeURIComponent( diff --git a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx index 52c25494a3a1..4b85b8f44be7 100644 --- a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx +++ b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { ComponentContext } from '@teambit/component'; import { H1 } from '@teambit/documenter.ui.heading'; import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile'; -import { Link } from '@teambit/base-react.navigation.link'; +import { useLocation, Link } from '@teambit/base-react.navigation.link'; import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser'; import { HoverSplitter } from '@teambit/base-ui.surfaces.split-pane.hover-splitter'; @@ -35,9 +35,7 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { const sidebarOpenness = isSidebarOpen ? Layout.row : Layout.left; const selectedAPIFromUrl = useAPIRefParam('selectedAPI'); - React.useEffect(() => { - window.scrollTo(0, 0); - }, [selectedAPIFromUrl]); + const apiNodes = (apiModel && flatten(Array.from(apiModel.apiByType.values())).sort(sortAPINodes)) || []; const isEmpty = apiNodes.length === 0; @@ -60,7 +58,7 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { (selectedAPINode && `${selectedAPINode?.renderer?.nodeType}/${selectedAPINode?.api.name}`) || apiTree[0]; const SelectedAPIComponent = selectedAPINode && selectedAPINode.renderer.Component; - // const location = useLocation(); + const location = useLocation(); const query = useQuery(); if (loading) { @@ -74,14 +72,18 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { if (!apiModel || isEmpty) { return ; } + const icon = selectedAPINode.renderer.icon; const name = selectedAPINode.api.name; const componentVersionFromUrl = query.get('version'); const filePath = selectedAPINode.api.location.filePath; - const pathname = ComponentUrl.toUrl(component.id, { includeVersion: false, useLocationOrigin: true }); + const pathname = + location?.pathname && window?.location?.hostname?.startsWith('localhost') + ? location?.pathname + : `${ComponentUrl.toUrl(component.id, { includeVersion: false })}/`; const componentUrlWithoutVersion = pathname?.split('~')[0]; - const locationUrl = `${componentUrlWithoutVersion}/~code/${filePath}${ + const locationUrl = `${componentUrlWithoutVersion}~code/${filePath}${ componentVersionFromUrl ? `?version=${componentVersionFromUrl}` : '' }`; diff --git a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts index 4ea7666d42f5..72f32f0f465a 100644 --- a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts +++ b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts @@ -21,7 +21,6 @@ export const defaultCodeEditorOptions: monaco.editor.IStandaloneEditorConstructi enabled: false, }, renderLineHighlight: 'none', - lineHeight: 20, - padding: { top: 8, bottom: 8 }, - cursorBlinking: 'smooth', + lineHeight: 18, + padding: { top: 8 }, }; diff --git a/scopes/code/ui/code-editor/code-editor.tsx b/scopes/code/ui/code-editor/code-editor.tsx index 02fe06f29dbf..b66dd3dc454e 100644 --- a/scopes/code/ui/code-editor/code-editor.tsx +++ b/scopes/code/ui/code-editor/code-editor.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import Editor, { OnMount, BeforeMount } from '@monaco-editor/react'; +import Editor, { OnMount, BeforeMount, OnChange } from '@monaco-editor/react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { darkMode } from '@teambit/base-ui.theme.dark-theme'; @@ -8,12 +8,13 @@ export type CodeEditorProps = { filePath?: string; fileContent?: string; language?: string; - handleEditorDidMount?: OnMount; - handleEditorBeforeMount?: BeforeMount; - Loader?: React.ReactNode; - options?: monaco.editor.IStandaloneEditorConstructionOptions; - className?: string; height?: string; + className?: string; + options?: monaco.editor.IStandaloneEditorConstructionOptions; + beforeMount?: BeforeMount; + onMount?: OnMount; + onChange?: OnChange; + Loader?: React.ReactNode; }; export const DEFAULT_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = { @@ -29,7 +30,7 @@ export const DEFAULT_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstruction fixedOverflowWidgets: true, renderLineHighlight: 'none', lineHeight: 20, - padding: { top: 8 }, + padding: { top: 8, bottom: 8 }, hover: { enabled: false }, cursorBlinking: 'smooth', }; @@ -48,8 +49,9 @@ export function CodeEditor({ fileContent, filePath, language, - handleEditorBeforeMount, - handleEditorDidMount, + beforeMount, + onMount, + onChange, Loader, options, className, @@ -67,10 +69,11 @@ export function CodeEditor({ value={fileContent || undefined} language={language || defaultLang} height={height || '100%'} - onMount={handleEditorDidMount} + onMount={onMount} + beforeMount={beforeMount} + onChange={onChange} className={classnames(darkMode, className)} theme={'vs-dark'} - beforeMount={handleEditorBeforeMount} options={options || DEFAULT_EDITOR_OPTIONS} loading={Loader} /> From d6285055c0676212a1f10a898e739bafa111c932 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Mon, 29 May 2023 14:36:35 -0400 Subject: [PATCH 12/12] fixes --- components/ui/code-view/code-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/code-view/code-view.tsx b/components/ui/code-view/code-view.tsx index dbbe4342d7dc..6dd9a6a08664 100644 --- a/components/ui/code-view/code-view.tsx +++ b/components/ui/code-view/code-view.tsx @@ -113,7 +113,7 @@ export function CodeView({ language={language} fileContent={fileContent} filePath={currentFile} - handleEditorDidMount={handleEditorDidMount} + onMount={handleEditorDidMount} Loader={} /> ),