diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /doc/development/fe_guide | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (diff) |
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'doc/development/fe_guide')
-rw-r--r-- | doc/development/fe_guide/accessibility.md | 2 | ||||
-rw-r--r-- | doc/development/fe_guide/content_editor.md | 6 | ||||
-rw-r--r-- | doc/development/fe_guide/editor_lite.md | 265 | ||||
-rw-r--r-- | doc/development/fe_guide/frontend_faq.md | 2 | ||||
-rw-r--r-- | doc/development/fe_guide/graphql.md | 29 | ||||
-rw-r--r-- | doc/development/fe_guide/index.md | 2 | ||||
-rw-r--r-- | doc/development/fe_guide/performance.md | 18 | ||||
-rw-r--r-- | doc/development/fe_guide/source_editor.md | 264 | ||||
-rw-r--r-- | doc/development/fe_guide/storybook.md | 51 | ||||
-rw-r--r-- | doc/development/fe_guide/style/html.md | 2 | ||||
-rw-r--r-- | doc/development/fe_guide/style/scss.md | 4 | ||||
-rw-r--r-- | doc/development/fe_guide/vue3_migration.md | 49 | ||||
-rw-r--r-- | doc/development/fe_guide/vuex.md | 57 |
13 files changed, 467 insertions, 284 deletions
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md index 15818941b24..0cd7cf58b58 100644 --- a/doc/development/fe_guide/accessibility.md +++ b/doc/development/fe_guide/accessibility.md @@ -510,7 +510,7 @@ Proper research and testing should be done to ensure compliance with [WCAG](http ### Viewing the browser accessibility tree - [Firefox DevTools guide](https://developer.mozilla.org/en-US/docs/Tools/Accessibility_inspector#accessing_the_accessibility_inspector) -- [Chrome DevTools guide](https://developers.google.com/web/tools/chrome-devtools/accessibility/reference#pane) +- [Chrome DevTools guide](https://developer.chrome.com/docs/devtools/accessibility/reference#pane) ### Browser extensions diff --git a/doc/development/fe_guide/content_editor.md b/doc/development/fe_guide/content_editor.md index f6329f39636..6cf4076bf83 100644 --- a/doc/development/fe_guide/content_editor.md +++ b/doc/development/fe_guide/content_editor.md @@ -54,7 +54,7 @@ the status of the ongoing development for CommonMark and GitLab Flavored Markdow To include the Content Editor in your feature, import the `createContentEditor` factory function and the `ContentEditor` Vue component. `createContentEditor` sets up an instance -of [tiptap's Editor class](https://www.tiptap.dev/api/editor) with all the necessary +of [tiptap's Editor class](https://www.tiptap.dev/api/editor/) with all the necessary extensions to support editing GitLab Flavored Markdown content. It also creates a Markdown serializer that allows exporting tiptap's document format to Markdown. @@ -90,7 +90,9 @@ export default { try { await this.contentEditor.setSerializedContent(this.content); } catch (e) { - createFlash(__('There was an error loading content in the editor'), e); + createFlash({ + message: __('There was an error loading content in the editor'), error: e + }); } }, methods: { diff --git a/doc/development/fe_guide/editor_lite.md b/doc/development/fe_guide/editor_lite.md index f28588c23e9..5020bf9eeeb 100644 --- a/doc/development/fe_guide/editor_lite.md +++ b/doc/development/fe_guide/editor_lite.md @@ -1,264 +1,9 @@ --- -stage: Create -group: Editor -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +redirect_to: 'source_editor.md' +remove_date: '2021-09-19' --- -# Editor Lite **(FREE)** +This document was moved to [another location](source_editor.md). -**Editor Lite** provides the editing experience at GitLab. This thin wrapper around -[the Monaco editor](https://microsoft.github.io/monaco-editor/) provides necessary -helpers and abstractions, and extends Monaco [using extensions](#extensions). Multiple -GitLab features use it, including: - -- [Web IDE](../../user/project/web_ide/index.md) -- [CI Linter](../../ci/lint.md) -- [Snippets](../../user/snippets.md) -- [Web Editor](../../user/project/repository/web_editor.md) -- [Security Policies](../../user/application_security/threat_monitoring/index.md) - -## How to use Editor Lite - -Editor Lite is framework-agnostic and can be used in any application, including both -Rails and Vue. To help with integration, we have the dedicated `<editor-lite>` -Vue component, but the integration of Editor Lite is generally straightforward: - -1. Import Editor Lite: - - ```javascript - import EditorLite from '~/editor/editor_lite'; - ``` - -1. Initialize global editor for the view: - - ```javascript - const editor = new EditorLite({ - // Editor Options. - // The list of all accepted options can be found at - // https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html - }); - ``` - -1. Create an editor's instance: - - ```javascript - editor.createInstance({ - // Editor Lite configuration options. - }) - ``` - -An instance of Editor Lite accepts the following configuration options: - -| Option | Required? | Description | -| -------------- | ------- | ---- | -| `el` | `true` | `HTML Node`: The element on which to render the editor. | -| `blobPath` | `false` | `String`: The name of a file to render in the editor, used to identify the correct syntax highlighter to use with that file, or another file type. Can accept wildcards like `*.js` when the actual filename isn't known or doesn't play any role. | -| `blobContent` | `false` | `String`: The initial content to render in the editor. | -| `extensions` | `false` | `Array`: Extensions to use in this instance. | -| `blobGlobalId` | `false` | `String`: An auto-generated property.<br>**Note:** This property may go away in the future. Do not pass `blobGlobalId` unless you know what you're doing.| -| Editor Options | `false` | `Object(s)`: Any property outside of the list above is treated as an Editor Option for this particular instance. Use this field to override global Editor Options on the instance level. A full [index of Editor Options](https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html) is available. | - -## API - -The editor uses the same public API as -[provided by Monaco editor](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) -with additional functions on the instance level: - -| Function | Arguments | Description -| --------------------- | ----- | ----- | -| `updateModelLanguage` | `path`: String | Updates the instance's syntax highlighting to follow the extension of the passed `path`. Available only on the instance level.| -| `use` | Array of objects | Array of extensions to apply to the instance. Accepts only the array of _objects_. You must fetch the extensions' ES6 modules must be fetched and resolved in your views or components before they are passed to `use`. This property is available on _instance_ (applies extension to this particular instance) and _global editor_ (applies the same extension to all instances) levels. | -| Monaco Editor options | See [documentation](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) | Default Monaco editor options | - -## Tips - -1. Editor's loading state. - - The loading state is built in to Editor Lite, making spinners and loaders - rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` - property on the HTML element that should contain the editor. When bootstrapping, - Editor Lite shows the loader automatically. - - ![Editor Lite: loading state](img/editor_lite_loading.png) - -1. Update syntax highlighting if the filename changes. - - ```javascript - // fileNameEl here is the HTML input element that contains the file name - fileNameEl.addEventListener('change', () => { - this.editor.updateModelLanguage(fileNameEl.value); - }); - ``` - -1. Get the editor's content. - - We may set up listeners on the editor for every change, but it rapidly can become - an expensive operation. Instead, get the editor's content when it's needed. - For example, on a form's submission: - - ```javascript - form.addEventListener('submit', () => { - my_content_variable = this.editor.getValue(); - }); - ``` - -1. Performance - - Even though Editor Lite itself is extremely slim, it still depends on Monaco editor, - which adds weight. Every time you add Editor Lite to a view, the JavaScript bundle's - size significantly increases, affecting your view's loading performance. We recommend - you import the editor on demand if either: - - - You're uncertain if the view needs the editor. - - The editor is a secondary element of the view. - - Loading Editor Lite on demand is handled like loading any other module: - - ```javascript - someActionFunction() { - import(/* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite'). - then(({ default: EditorLite }) => { - const editor = new EditorLite(); - ... - }); - ... - } - ``` - -## Extensions - -Editor Lite provides a universal, extensible editing tool to the whole product, -and doesn't depend on any particular group. Even though the Editor Lite's core is owned by -[Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/), -any group can own the extensions—the main functional elements. The goal of -Editor Lite extensions is to keep the editor's core slim and stable. Any -needed features can be added as extensions to this core. Any group can -build and own new editing features without worrying about changes to Editor Lite -breaking or overriding them. - -You can depend on other modules in your extensions. This organization helps keep -the size of Editor Lite's core at bay by importing dependencies only when needed. - -Structurally, the complete implementation of Editor Lite can be presented as this diagram: - -```mermaid -graph TD; - B[Extension 1]---A[Editor Lite] - C[Extension 2]---A[Editor Lite] - D[Extension 3]---A[Editor Lite] - E[...]---A[Editor Lite] - F[Extension N]---A[Editor Lite] - A[Editor Lite]---Z[Monaco] -``` - -An extension is an ES6 module that exports a JavaScript object: - -```javascript -import { Position } from 'monaco-editor'; - -export default { - navigateFileStart() { - this.setPosition(new Position(1, 1)); - }, -}; - -``` - -In the extension's functions, `this` refers to the current Editor Lite instance. -Using `this`, you get access to the complete instance's API, such as the -`setPosition()` method in this particular case. - -### Using an existing extension - -Adding an extension to Editor Lite's instance requires the following steps: - -```javascript -import EditorLite from '~/editor/editor_lite'; -import MyExtension from '~/my_extension'; - -const editor = new EditorLite().createInstance({ - ... -}); -editor.use(MyExtension); -``` - -### Creating an extension - -Let's create our first Editor Lite extension. Extensions are -[ES6 modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/) exporting a -basic `Object`, used to extend Editor Lite's features. As a test, let's -create an extension that extends Editor Lite with a new function that, when called, -outputs the editor's content in `alert`. - -`~/my_folder/my_fancy_extension.js:` - -```javascript -export default { - throwContentAtMe() { - alert(this.getValue()); - }, -}; -``` - -In the code example, `this` refers to the instance. By referring to the instance, -we can access the complete underlying -[Monaco editor API](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html), -which includes functions like `getValue()`. - -Now let's use our extension: - -`~/my_folder/component_bundle.js`: - -```javascript -import EditorLite from '~/editor/editor_lite'; -import MyFancyExtension from './my_fancy_extension'; - -const editor = new EditorLite().createInstance({ - ... -}); -editor.use(MyFancyExtension); -... -someButton.addEventListener('click', () => { - editor.throwContentAtMe(); -}); -``` - -First of all, we import Editor Lite and our new extension. Then we create the -editor and its instance. By default Editor Lite has no `throwContentAtMe` method. -But the `editor.use(MyFancyExtension)` line brings that method to our instance. -After that, we can use it any time we need it. In this case, we call it when some -theoretical button has been clicked. - -This script would result in an alert containing the editor's content when `someButton` is clicked. - -![Editor Lite new extension's result](img/editor_lite_create_ext.png) - -### Tips - -1. Performance - - Just like Editor Lite itself, any extension can be loaded on demand to not harm - loading performance of the views: - - ```javascript - const EditorPromise = import( - /* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite' - ); - const MarkdownExtensionPromise = import('~/editor/editor_markdown_ext'); - - Promise.all([EditorPromise, MarkdownExtensionPromise]) - .then(([{ default: EditorLite }, { default: MarkdownExtension }]) => { - const editor = new EditorLite().createInstance({ - ... - }); - editor.use(MarkdownExtension); - }); - ``` - -1. Using multiple extensions - - Just pass the array of extensions to your `use` method: - - ```javascript - editor.use([FileTemplateExtension, MyFancyExtension]); - ``` +<!-- This redirect file can be deleted after <2021-09-19>. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page --> diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md index 6b9d5ace4e6..1e8f7f5fb81 100644 --- a/doc/development/fe_guide/frontend_faq.md +++ b/doc/development/fe_guide/frontend_faq.md @@ -157,7 +157,7 @@ export const fetchFoos = ({ state }) => { Sometimes it's necessary to test locally what the frontend production build would produce, to do so the steps are: 1. Stop webpack: `gdk stop webpack`. -1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change `dev_server` to `enabled: false`. +1. Open `gitlab.yaml` located in `gitlab/config` folder, scroll down to the `webpack` section, and change `dev_server` to `enabled: false`. 1. Run `yarn webpack-prod && gdk restart rails-web`. The production build takes a few minutes to be completed. Any code changes at this point are diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index 844ef2156d9..7fa9e957f56 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -906,6 +906,35 @@ apollo: { }, ``` +### Best Practices + +#### When to use (and not use) `update` hook in mutations + +Apollo Client's [`.mutate()`](https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.mutate) +method exposes an `update` hook that is invoked twice during the mutation lifecycle: + +- Once at the beginning. That is, before the mutation has completed. +- Once after the mutation has completed. + +You should use this hook only if you're adding or removing an item from the store +(that is, ApolloCache). If you're _updating_ an existing item, it is usually represented by +a global `id`. + +In that case, presence of this `id` in your mutation query definition makes the store update +automatically. Here's an example of a typical mutation query with `id` present in it: + +```graphql +mutation issueSetWeight($input: IssueSetWeightInput!) { + issuableSetWeight: issueSetWeight(input: $input) { + issuable: issue { + id + weight + } + errors + } +} +``` + ### Testing #### Generating the GraphQL schema diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 00f0d72571a..325310ad05c 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -95,7 +95,7 @@ How we implement [keyboard shortcuts](keyboard_shortcuts.md) that can be customi ## Editors -GitLab text editing experiences are provided by the [Source Editor](editor_lite.md) and +GitLab text editing experiences are provided by the [Source Editor](source_editor.md) and the [Content Editor](content_editor.md). ## Frontend FAQ diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md index dd3945ae324..e7f347554d7 100644 --- a/doc/development/fe_guide/performance.md +++ b/doc/development/fe_guide/performance.md @@ -255,18 +255,18 @@ We support two types of prefetching for the chunks: - The [`prefetch` link type](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/prefetch) is used to prefetch a chunk for the future navigation -- The [`preload` link type](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preloadh) - is used to prefetch a chunk that is crucial for the current navigation but is not +- The [`preload` link type](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload) + is used to prefetch a chunk that is crucial for the current navigation but is not discovered until later in the rendering process -Both `prefetch` and `preload` links bring the loading performance benefit to the pages. Both are +Both `prefetch` and `preload` links bring the loading performance benefit to the pages. Both are fetched asynchronously, but contrary to [deferring the loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer) of the assets which is used for other JavaScript resources in the product by default, `prefetch` and -`preload` neither parse nor execute the fetched script unless explicitly imported in any JavaScript -module. This allows to cache the fetched resources without blocking the execution of the +`preload` neither parse nor execute the fetched script unless explicitly imported in any JavaScript +module. This allows to cache the fetched resources without blocking the execution of the remaining page resources. -To prefetch a JavaScript chunk in a HAML view, `:prefetch_asset_tags` with the combination of +To prefetch a JavaScript chunk in a HAML view, `:prefetch_asset_tags` with the combination of the `webpack_preload_asset_tag` helper is provided: ```javascript @@ -280,8 +280,8 @@ This snippet will add a new `<link rel="preload">` element into the resulting HT <link rel="preload" href="/assets/webpack/monaco.chunk.js" as="script" type="text/javascript"> ``` -By default, `webpack_preload_asset_tag` will `preload` the chunk. You don't need to worry about -`as` and `type` attributes for preloading the JavaScript chunks. However, when a chunk is not +By default, `webpack_preload_asset_tag` will `preload` the chunk. You don't need to worry about +`as` and `type` attributes for preloading the JavaScript chunks. However, when a chunk is not critical, for the current navigation, one has to explicitly request `prefetch`: ```javascript @@ -454,5 +454,5 @@ General tips: - [WebPage Test](https://www.webpagetest.org) for testing site loading time and size. - [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) grades web pages and provides feedback to improve the page. -- [Profiling with Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/) +- [Profiling with Chrome DevTools](https://developer.chrome.com/docs/devtools/) - [Browser Diet](https://browserdiet.com/) is a community-built guide that catalogues practical tips for improving web page performance. diff --git a/doc/development/fe_guide/source_editor.md b/doc/development/fe_guide/source_editor.md new file mode 100644 index 00000000000..fc128c0ecb1 --- /dev/null +++ b/doc/development/fe_guide/source_editor.md @@ -0,0 +1,264 @@ +--- +stage: Create +group: Editor +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Source Editor **(FREE)** + +**Source Editor** provides the editing experience at GitLab. This thin wrapper around +[the Monaco editor](https://microsoft.github.io/monaco-editor/) provides necessary +helpers and abstractions, and extends Monaco [using extensions](#extensions). Multiple +GitLab features use it, including: + +- [Web IDE](../../user/project/web_ide/index.md) +- [CI Linter](../../ci/lint.md) +- [Snippets](../../user/snippets.md) +- [Web Editor](../../user/project/repository/web_editor.md) +- [Security Policies](../../user/application_security/threat_monitoring/index.md) + +## How to use Source Editor + +Source Editor is framework-agnostic and can be used in any application, including both +Rails and Vue. To help with integration, we have the dedicated `<source-editor>` +Vue component, but the integration of Source Editor is generally straightforward: + +1. Import Source Editor: + + ```javascript + import SourceEditor from '~/editor/source_editor'; + ``` + +1. Initialize global editor for the view: + + ```javascript + const editor = new SourceEditor({ + // Editor Options. + // The list of all accepted options can be found at + // https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html + }); + ``` + +1. Create an editor's instance: + + ```javascript + editor.createInstance({ + // Source Editor configuration options. + }) + ``` + +An instance of Source Editor accepts the following configuration options: + +| Option | Required? | Description | +| -------------- | ------- | ---- | +| `el` | `true` | `HTML Node`: The element on which to render the editor. | +| `blobPath` | `false` | `String`: The name of a file to render in the editor, used to identify the correct syntax highlighter to use with that file, or another file type. Can accept wildcards like `*.js` when the actual filename isn't known or doesn't play any role. | +| `blobContent` | `false` | `String`: The initial content to render in the editor. | +| `extensions` | `false` | `Array`: Extensions to use in this instance. | +| `blobGlobalId` | `false` | `String`: An auto-generated property.<br>**Note:** This property may go away in the future. Do not pass `blobGlobalId` unless you know what you're doing.| +| Editor Options | `false` | `Object(s)`: Any property outside of the list above is treated as an Editor Option for this particular instance. Use this field to override global Editor Options on the instance level. A full [index of Editor Options](https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html) is available. | + +## API + +The editor uses the same public API as +[provided by Monaco editor](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) +with additional functions on the instance level: + +| Function | Arguments | Description +| --------------------- | ----- | ----- | +| `updateModelLanguage` | `path`: String | Updates the instance's syntax highlighting to follow the extension of the passed `path`. Available only on the instance level.| +| `use` | Array of objects | Array of extensions to apply to the instance. Accepts only the array of _objects_. You must fetch the extensions' ES6 modules must be fetched and resolved in your views or components before they are passed to `use`. This property is available on _instance_ (applies extension to this particular instance) and _global editor_ (applies the same extension to all instances) levels. | +| Monaco Editor options | See [documentation](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) | Default Monaco editor options | + +## Tips + +1. Editor's loading state. + + The loading state is built in to Source Editor, making spinners and loaders + rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` + property on the HTML element that should contain the editor. When bootstrapping, + Source Editor shows the loader automatically. + + ![Source Editor: loading state](img/editor_lite_loading.png) + +1. Update syntax highlighting if the filename changes. + + ```javascript + // fileNameEl here is the HTML input element that contains the file name + fileNameEl.addEventListener('change', () => { + this.editor.updateModelLanguage(fileNameEl.value); + }); + ``` + +1. Get the editor's content. + + We may set up listeners on the editor for every change, but it rapidly can become + an expensive operation. Instead, get the editor's content when it's needed. + For example, on a form's submission: + + ```javascript + form.addEventListener('submit', () => { + my_content_variable = this.editor.getValue(); + }); + ``` + +1. Performance + + Even though Source Editor itself is extremely slim, it still depends on Monaco editor, + which adds weight. Every time you add Source Editor to a view, the JavaScript bundle's + size significantly increases, affecting your view's loading performance. We recommend + you import the editor on demand if either: + + - You're uncertain if the view needs the editor. + - The editor is a secondary element of the view. + + Loading Source Editor on demand is handled like loading any other module: + + ```javascript + someActionFunction() { + import(/* webpackChunkName: 'SourceEditor' */ '~/editor/source_editor'). + then(({ default: SourceEditor }) => { + const editor = new SourceEditor(); + ... + }); + ... + } + ``` + +## Extensions + +Source Editor provides a universal, extensible editing tool to the whole product, +and doesn't depend on any particular group. Even though the Source Editor's core is owned by +[Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/), +any group can own the extensions—the main functional elements. The goal of +Source Editor extensions is to keep the editor's core slim and stable. Any +needed features can be added as extensions to this core. Any group can +build and own new editing features without worrying about changes to Source Editor +breaking or overriding them. + +You can depend on other modules in your extensions. This organization helps keep +the size of Source Editor's core at bay by importing dependencies only when needed. + +Structurally, the complete implementation of Source Editor can be presented as this diagram: + +```mermaid +graph TD; + B[Extension 1]---A[Source Editor] + C[Extension 2]---A[Source Editor] + D[Extension 3]---A[Source Editor] + E[...]---A[Source Editor] + F[Extension N]---A[Source Editor] + A[Source Editor]---Z[Monaco] +``` + +An extension is an ES6 module that exports a JavaScript object: + +```javascript +import { Position } from 'monaco-editor'; + +export default { + navigateFileStart() { + this.setPosition(new Position(1, 1)); + }, +}; + +``` + +In the extension's functions, `this` refers to the current Source Editor instance. +Using `this`, you get access to the complete instance's API, such as the +`setPosition()` method in this particular case. + +### Using an existing extension + +Adding an extension to Source Editor's instance requires the following steps: + +```javascript +import SourceEditor from '~/editor/source_editor'; +import MyExtension from '~/my_extension'; + +const editor = new SourceEditor().createInstance({ + ... +}); +editor.use(MyExtension); +``` + +### Creating an extension + +Let's create our first Source Editor extension. Extensions are +[ES6 modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/) exporting a +basic `Object`, used to extend Source Editor's features. As a test, let's +create an extension that extends Source Editor with a new function that, when called, +outputs the editor's content in `alert`. + +`~/my_folder/my_fancy_extension.js:` + +```javascript +export default { + throwContentAtMe() { + alert(this.getValue()); + }, +}; +``` + +In the code example, `this` refers to the instance. By referring to the instance, +we can access the complete underlying +[Monaco editor API](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html), +which includes functions like `getValue()`. + +Now let's use our extension: + +`~/my_folder/component_bundle.js`: + +```javascript +import SourceEditor from '~/editor/source_editor'; +import MyFancyExtension from './my_fancy_extension'; + +const editor = new SourceEditor().createInstance({ + ... +}); +editor.use(MyFancyExtension); +... +someButton.addEventListener('click', () => { + editor.throwContentAtMe(); +}); +``` + +First of all, we import Source Editor and our new extension. Then we create the +editor and its instance. By default Source Editor has no `throwContentAtMe` method. +But the `editor.use(MyFancyExtension)` line brings that method to our instance. +After that, we can use it any time we need it. In this case, we call it when some +theoretical button has been clicked. + +This script would result in an alert containing the editor's content when `someButton` is clicked. + +![Source Editor new extension's result](img/editor_lite_create_ext.png) + +### Tips + +1. Performance + + Just like Source Editor itself, any extension can be loaded on demand to not harm + loading performance of the views: + + ```javascript + const EditorPromise = import( + /* webpackChunkName: 'SourceEditor' */ '~/editor/source_editor' + ); + const MarkdownExtensionPromise = import('~/editor/source_editor_markdown_ext'); + + Promise.all([EditorPromise, MarkdownExtensionPromise]) + .then(([{ default: SourceEditor }, { default: MarkdownExtension }]) => { + const editor = new SourceEditor().createInstance({ + ... + }); + editor.use(MarkdownExtension); + }); + ``` + +1. Using multiple extensions + + Just pass the array of extensions to your `use` method: + + ```javascript + editor.use([FileTemplateExtension, MyFancyExtension]); + ``` diff --git a/doc/development/fe_guide/storybook.md b/doc/development/fe_guide/storybook.md new file mode 100644 index 00000000000..15225cc1deb --- /dev/null +++ b/doc/development/fe_guide/storybook.md @@ -0,0 +1,51 @@ +--- +stage: none +group: unassigned +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Storybook + +The Storybook for the `gitlab-org/gitlab` project is available on our [GitLab Pages site](https://gitlab-org.gitlab.io/gitlab/storybook). + +## Storybook in local development + +Storybook dependencies and configuration are located under the `storybook/` directory. + +To build and launch Storybook locally, in the root directory of the `gitlab` project: + +1. Install Storybook dependencies: + + ```shell + yarn storybook:install + ``` + +1. Build the Storybook site: + + ```shell + yarn storybook:start + ``` + +## Adding components to Storybook + +Stories can be added for any Vue component in the `gitlab` repository. + +To add a story: + +1. Create a new `.stories.js` file in the same directory as the Vue component. + The file name should have the same prefix as the Vue component. + + ```txt + vue_shared/ + ├─ components/ + │ ├─ sidebar + │ │ ├─ todo_button.vue + │ │ ├─ todo_button.stories.js + ``` + +1. Write the story as per the [official Storybook instructions](https://storybook.js.org/docs/vue/writing-stories/introduction/) + + Notes: + - Specify the `title` field of the story as the component's file path from the `javascripts/` directory, + e.g. if the component is located at `app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.vue`, specify the `title` as + `vue_shared/components/To-do Button`. This will ensure the Storybook navigation maps closely to our internal directory structure. diff --git a/doc/development/fe_guide/style/html.md b/doc/development/fe_guide/style/html.md index 18f72a9655c..72492d56ee4 100644 --- a/doc/development/fe_guide/style/html.md +++ b/doc/development/fe_guide/style/html.md @@ -62,7 +62,7 @@ Avoid forcing links to open in a new window as this reduces the control the user However, it might be a good idea to use a blank target when replacing the current page with the link makes the user lose content or progress. -Use `rel="noopener noreferrer"` whenever your links open in a new window, i.e. `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/). +Use `rel="noopener noreferrer"` whenever your links open in a new window, that is, `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/). When using `gl-link`, using `target="_blank"` is sufficient as it automatically adds `rel="noopener noreferrer"` to the link. diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index c0817626360..4a9446f2949 100644 --- a/doc/development/fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md @@ -51,7 +51,7 @@ We recommend a "utility-first" approach. 1. Start with utility classes. 1. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it. -This encourages an organic growth of component classes and prevents the creation of one-off non-reusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (e.g. `.button`, `.alert`, `.card`) rather than domain-centered (e.g. `.security-report-widget`, `.commit-header-icon`). +This encourages an organic growth of component classes and prevents the creation of one-off non-reusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (for example, `.button`, `.alert`, `.card`) rather than domain-centered (for example, `.security-report-widget`, `.commit-header-icon`). Inspiration: @@ -139,7 +139,7 @@ To check if any warnings are produced by your changes, run `yarn lint:stylelint` catch any warnings. If the Rake task is throwing warnings you don't understand, SCSS Lint's -documentation includes [a full list of their rules](https://stylelint.io/user-guide/rules/list). +documentation includes [a full list of their rules](https://stylelint.io/user-guide/rules/list/). ### Fixing issues diff --git a/doc/development/fe_guide/vue3_migration.md b/doc/development/fe_guide/vue3_migration.md index d06e93da0f3..7da462a5f89 100644 --- a/doc/development/fe_guide/vue3_migration.md +++ b/doc/development/fe_guide/vue3_migration.md @@ -8,7 +8,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w Preparations for a Vue 3 migration are tracked in epic [&3174](https://gitlab.com/groups/gitlab-org/-/epics/3174) -In order to prepare for the eventual migration to Vue 3.x, we should be wary about adding the following features to the codebase: +In order to prepare for the eventual migration to Vue 3.x, we should not use the following deprecated features in the codebase: + +NOTE: +Our linting rules block the use of these deprecated features. ## Vue filters @@ -132,3 +135,47 @@ shallowMount(MyAwesomeComponent, { } }) ``` + +## Props default function `this` access + +**Why?** + +In Vue 3, props default value factory functions no longer have access to `this` +(the component instance). + +**What to use instead** + +Write a computed prop that resolves the desired value from other props. This +will work in both Vue 2 and 3. + +```html +<script> +export default { + props: { + metric: { + type: String, + required: true, + }, + title: { + type: String, + required: false, + default: null, + }, + }, + computed: { + actualTitle() { + return this.title ?? this.metric; + }, + }, +} + +</script> + +<template> + <div>{{ actualTitle }}</div> +</template> +``` + +[In Vue 3](https://v3.vuejs.org/guide/migration/props-default-this.html#props-default-function-this-access), +the props default value factory is passed the raw props as an argument, and can +also access injections. diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 3d0044928f1..064f01c8195 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -106,7 +106,7 @@ In this file, we write the actions that call mutations for handling a list of us .then(({ data }) => commit(types.RECEIVE_USERS_SUCCESS, data)) .catch((error) => { commit(types.RECEIVE_USERS_ERROR, error) - createFlash('There was an error') + createFlash({ message: 'There was an error' }) }); } @@ -540,11 +540,11 @@ export default { foo: '' }, actions: { - updateBar() {...} - updateAll() {...} + updateBar() {...}, + updateAll() {...}, }, getters: { - getFoo() {...} + getFoo() {...}, } } ``` @@ -559,13 +559,13 @@ export default { * @param {string} list[].getter - the name of the getter, leave it empty to not use a getter * @param {string} list[].updateFn - the name of the action, leave it empty to use the default action * @param {string} defaultUpdateFn - the default function to dispatch - * @param {string} root - optional key of the state where to search fo they keys described in list + * @param {string|function} root - optional key of the state where to search for they keys described in list * @returns {Object} a dictionary with all the computed properties generated */ ...mapComputed( [ 'baz', - { key: 'bar', updateFn: 'updateBar' } + { key: 'bar', updateFn: 'updateBar' }, { key: 'foo', getter: 'getFoo' }, ], 'updateAll', @@ -575,3 +575,48 @@ export default { ``` `mapComputed` then generates the appropriate computed properties that get the data from the store and dispatch the correct action when updated. + +In the event that the `root` of the key is more than one-level deep you can use a function to retrieve the relevant state object. + +For instance, with a store like: + +```javascript +// this store is non-functional and only used to give context to the example +export default { + state: { + foo: { + qux: { + baz: '', + bar: '', + foo: '', + }, + }, + }, + actions: { + updateBar() {...}, + updateAll() {...}, + }, + getters: { + getFoo() {...}, + } +} +``` + +The `root` could be: + +```javascript +import { mapComputed } from '~/vuex_shared/bindings' +export default { + computed: { + ...mapComputed( + [ + 'baz', + { key: 'bar', updateFn: 'updateBar' }, + { key: 'foo', getter: 'getFoo' }, + ], + 'updateAll', + (state) => state.foo.qux, + ), + } +} +``` |