diff --git a/.eslintrc.yml b/.eslintrc.yml index f5814639b36..7f45fd912a9 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -93,6 +93,10 @@ rules: group: internal alphabetize: order: ignore + 'no-restricted-syntax': + - error + - selector: ImportSpecifier[imported.name='GlSkeletonLoading'] + message: 'Migrate to GlSkeletonLoader, or import GlDeprecatedSkeletonLoading.' overrides: - files: - '{,ee/,jh/}spec/frontend*/**/*' @@ -107,6 +111,8 @@ overrides: message: 'Using $nextTick from a component instance is discouraged. Import nextTick directly from the Vue package.' - selector: Identifier[name='setImmediate'] message: 'Prefer explicit waitForPromises (or equivalent), or jest.runAllTimers (or equivalent) to vague setImmediate calls.' + - selector: ImportSpecifier[imported.name='GlSkeletonLoading'] + message: 'Migrate to GlSkeletonLoader, or import GlDeprecatedSkeletonLoading.' - files: - 'config/**/*' - 'scripts/**/*' diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index d3ea9682d34..49d1d0f79bf 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -8,17 +8,17 @@ ## Author's checklist -- [ ] Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4). +- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4). - [ ] Follow the: - [Documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html). - [Documentation guidelines](https://docs.gitlab.com/ee/development/documentation/). - [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/). -- [ ] Ensure that the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#product-tier-badges) is added to topic's `h1`. -- [ ] [Request a review](https://docs.gitlab.com/ee/development/code_review.html#dogfooding-the-reviewers-feature) based on: - - The documentation page's [metadata](https://docs.gitlab.com/ee/development/documentation/#metadata). - - The [associated Technical Writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments). +- [ ] If you're adding or changing the main heading of the page (H1), ensure that the [product tier badge](https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#product-tier-badges) is added. +- [ ] If you are a GitLab team member, [request a review](https://docs.gitlab.com/ee/development/code_review.html#dogfooding-the-attention-request-feature) based on: + - The documentation page's [metadata](https://docs.gitlab.com/ee/development/documentation/#metadata). + - The [associated Technical Writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments). -If you are only adding documentation, do not add any of the following labels: +If you are a GitLab team member and only adding documentation, do not add any of the following labels: - `~"frontend"` - `~"backend"` @@ -27,7 +27,7 @@ If you are only adding documentation, do not add any of the following labels: These labels cause the MR to be added to code verification QA issues. -## Review checklist +## Reviewer's checklist Documentation-related MRs should be reviewed by a Technical Writer for a non-blocking review, based on [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and the [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/). @@ -35,13 +35,13 @@ Documentation-related MRs should be reviewed by a Technical Writer for a non-blo - Technical writer review items: - [ ] Ensure docs metadata is present and up-to-date. - [ ] Ensure the appropriate [labels](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#labels) are added to this MR. + - [ ] Ensure a release milestone is set. - If relevant to this MR, ensure [content topic type](https://docs.gitlab.com/ee/development/documentation/structure.html) principles are in use, including: - [ ] The headings should be something you'd do a Google search for. Instead of `Default behavior`, say something like `Default behavior when you close an issue`. - [ ] The headings (other than the page title) should be active. Instead of `Configuring GDK`, say something like `Configure GDK`. - [ ] Any task steps should be written as a numbered list. - If the content still needs to be edited for topic types, you can create a follow-up issue with the ~"docs-technical-debt" label. - [ ] Review by assigned maintainer, who can always request/require the reviews above. Maintainer's review can occur before or after a technical writer review. -- [ ] Ensure a release milestone is set. -/label ~documentation ~"type::maintenance" +/label ~documentation ~"type::maintenance" ~"docs::improvement" /assign me diff --git a/CHANGELOG.md b/CHANGELOG.md index 529a3904a0a..add5cacce7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 14.9.2 (2022-03-31) + +### Security (20 changes) + +- [Quarantine UsageDataNonSqlMetrics failing test](gitlab-org/security/gitlab@123fc00ff9f407284ce05007ddc373e1bd0aeede) ([merge request](gitlab-org/security/gitlab!2364)) +- [Disallow login if password matches a fixed list](gitlab-org/security/gitlab@1a128ae3fb17b3d83974bb08034e4ba7a7d54e3b) ([merge request](gitlab-org/security/gitlab!2357)) +- [Update devise-two-factor to 4.0.2](gitlab-org/security/gitlab@17c70b13dcd437c05de63b3286245af8e6f42210) ([merge request](gitlab-org/security/gitlab!2349)) +- [Limit the number of tags associated with a CI runner](gitlab-org/security/gitlab@ed5daced882a0206e050c4f676a888ac1c2417b1) ([merge request](gitlab-org/security/gitlab!2303)) +- [GitLab Pages Security Updates for 14.9](gitlab-org/security/gitlab@79709cabf71a57a336f490636a7e32a208fe0229) ([merge request](gitlab-org/security/gitlab!2327)) +- [Upgrade swagger-ui dependency](gitlab-org/security/gitlab@14280c1d844be3ffc2f30f5321a818a7b6c51770) ([merge request](gitlab-org/security/gitlab!2336)) +- [Modify release link format check to avoid regex if string is too long](gitlab-org/security/gitlab@f516d883b46e1441410476dc140d69fde51cdf0f) ([merge request](gitlab-org/security/gitlab!2307)) +- [Masks variables in error messages](gitlab-org/security/gitlab@9cf62118390c0cfba3d36a4231a30a7836f06e2f) ([merge request](gitlab-org/security/gitlab!2308)) +- [Escape user provided string to prevent XSS](gitlab-org/security/gitlab@2da3502aef64ed1b01c13d82418950cf284098c6) ([merge request](gitlab-org/security/gitlab!2313)) +- [Monkey patch of RDoc to prevent Ruby segfault](gitlab-org/security/gitlab@0ae4925089a1b5fd7c9abeeb0756b3a50e05799a) ([merge request](gitlab-org/security/gitlab!2321)) +- [Project import maps members' created_by_id users based on source user ID](gitlab-org/security/gitlab@3826f2a7c652d3f74e45bfef8888601ca1c86ba1) ([merge request](gitlab-org/security/gitlab!2301)) +- [Redact InvalidURIError error messages](gitlab-org/security/gitlab@59b60e9cf8f79d6f41000d34a4434c5a04988030) ([merge request](gitlab-org/security/gitlab!2295)) +- [Fix access for approval rules API](gitlab-org/security/gitlab@7890215aa29624cd67c5bc8ac25175f2866479b7) ([merge request](gitlab-org/security/gitlab!2322)) +- [Fix kroki exploit](gitlab-org/security/gitlab@b2a44b407ab85ca056a271ba4e708128ef08d25f) ([merge request](gitlab-org/security/gitlab!2306)) +- [Fix blind SSRF when looking up SSH host keys for mirroring](gitlab-org/security/gitlab@5a9509b52584302c508bd6dff1454f80aae371ea) ([merge request](gitlab-org/security/gitlab!2309)) +- [Escape original content in reference redactor](gitlab-org/security/gitlab@b33b170a2c2df8285999f3631e8a53d35e0eed22) ([merge request](gitlab-org/security/gitlab!2317)) +- [Security fix for CI/CD analytics visibility](gitlab-org/security/gitlab@f3febd00b440475b2aca0b9bd6728fa5f8750288) ([merge request](gitlab-org/security/gitlab!2304)) +- [Latest commit exposed through fork of a private project](gitlab-org/security/gitlab@3f20d4f294a12ceb33bec19d86790f582fb7fb48) ([merge request](gitlab-org/security/gitlab!2294)) +- [Fix Asana integration restricted branch filter](gitlab-org/security/gitlab@08aa0f55b1b715f7311ee6502cd6f8a1b875f878) ([merge request](gitlab-org/security/gitlab!2300)) +- [Revert "JH need more complex passwords"](gitlab-org/security/gitlab@e2fb87ec5d4e235d6b83454980cec9c049849a1c) ([merge request](gitlab-org/security/gitlab!2352)) + ## 14.9.1 (2022-03-23) ### Fixed (1 change) @@ -604,6 +629,32 @@ entry. - [Clean up issue_boards_filtered_search feature flag](gitlab-org/gitlab@a97ed09ffb0d88007b21a314ab48b2e50d7c4bfa) ([merge request](gitlab-org/gitlab!80771)) - [Add table for storing issue tsvector](gitlab-org/gitlab@ceabf5a8ad0d67768b05a58a84b242495645a57c) ([merge request](gitlab-org/gitlab!71913)) +## 14.8.5 (2022-03-31) + +### Security (21 changes) + +- [Update to commonmarker 0.23.4](gitlab-org/security/gitlab@51532ccc5f1b6b053d4ca6c54496607e62f8f25c) ([merge request](gitlab-org/security/gitlab!2282)) +- [Revert merge request approval groups behavior](gitlab-org/security/gitlab@dd9724e429033974da6c3852dc6fd33f0f2b0a46) ([merge request](gitlab-org/security/gitlab!2334)) +- [Disallow login if password matches a fixed list](gitlab-org/security/gitlab@6779d5f2948425a7ad7f19a6e10f82cc10b80989) ([merge request](gitlab-org/security/gitlab!2358)) +- [Update devise-two-factor to 4.0.2](gitlab-org/security/gitlab@0329d2d82a9064c0bae36e7b993ee40df7c999bc) ([merge request](gitlab-org/security/gitlab!2350)) +- [Limit the number of tags associated with a CI runner](gitlab-org/security/gitlab@8d5938c08fe66c22f1bc54ff76cc9daf2de86b1a) ([merge request](gitlab-org/security/gitlab!2302)) +- [GitLab Pages Security Updates for 14.9](gitlab-org/security/gitlab@5a5a862c8a9e37ca2ea84133f92b216eaa7cd148) ([merge request](gitlab-org/security/gitlab!2328)) +- [Upgrade swagger-ui dependency](gitlab-org/security/gitlab@afcb570867db61347bb6a4e243bb2557340191be) ([merge request](gitlab-org/security/gitlab!2337)) +- [Modify release link format check to avoid regex if string is too long](gitlab-org/security/gitlab@a3ab0ff9c470c1c6e5b4fd055ddd02dffce32652) ([merge request](gitlab-org/security/gitlab!2243)) +- [Masks variables in error messages](gitlab-org/security/gitlab@94236bbdb8eef6600562bdc4e242e07eaed8c50f) ([merge request](gitlab-org/security/gitlab!2291)) +- [Escape user provided string to prevent XSS](gitlab-org/security/gitlab@03e695d4c34546582b503b3f7712246206b56b99) ([merge request](gitlab-org/security/gitlab!2314)) +- [Monkey patch of RDoc to prevent Ruby segfault](gitlab-org/security/gitlab@14eec4487387bc0c999f1c48b046a3ed3848c5a1) ([merge request](gitlab-org/security/gitlab!2232)) +- [Project import maps members' created_by_id users based on source user ID](gitlab-org/security/gitlab@7fd7ab3f57e8d8b4e0aed42aebe9a8b7436a6255) ([merge request](gitlab-org/security/gitlab!2238)) +- [Redact InvalidURIError error messages](gitlab-org/security/gitlab@0592c182bfd60aee501c4c66f47a71c9469f2bcd) ([merge request](gitlab-org/security/gitlab!2296)) +- [Fix access for approval rules API](gitlab-org/security/gitlab@987e06bacba224519adf94cda73b5a8b2e7b917a) ([merge request](gitlab-org/security/gitlab!2323)) +- [Fix kroki exploit](gitlab-org/security/gitlab@bf056c683af25ec4b94c0efa7166eea399ed6502) ([merge request](gitlab-org/security/gitlab!2277)) +- [Fix blind SSRF when looking up SSH host keys for mirroring](gitlab-org/security/gitlab@3c853a32a73aba15e309d05111b744455a360cca) ([merge request](gitlab-org/security/gitlab!2310)) +- [Escape original content in reference redactor](gitlab-org/security/gitlab@00ee99bc3834d9d59572272064c9ad6abeae5975) ([merge request](gitlab-org/security/gitlab!2318)) +- [Security fix for CI/CD analytics visibility](gitlab-org/security/gitlab@691d69be77ae3c8e0a2598b75ccf336b672fd540) ([merge request](gitlab-org/security/gitlab!2273)) +- [Latest commit exposed through fork of a private project](gitlab-org/security/gitlab@6ca7a3b040edac06b23a697bfc2bf46f457d6b81) ([merge request](gitlab-org/security/gitlab!2271)) +- [Fix Asana integration restricted branch filter](gitlab-org/security/gitlab@4c1db692b4e99fab6cdbb818cf02fb879f6d4886) ([merge request](gitlab-org/security/gitlab!2218)) +- [Revert "JH need more complex passwords"](gitlab-org/security/gitlab@919aa2b28645d49fb71508362a0c61da39893c69) ([merge request](gitlab-org/security/gitlab!2353)) + ## 14.8.4 (2022-03-16) ### Added (1 change) @@ -1319,6 +1370,32 @@ entry. - [Use `ssh_data` gem instead of `net-ssh` and `sshkey` where possible](gitlab-org/gitlab@59a0ee8605d509753c9aec719f8e0da77bcc679d) ([merge request](gitlab-org/gitlab!77424)) - [Remove feature flag already default enabled](gitlab-org/gitlab@9b7059a4bf9dc2ecdce1910a931cc6967d05b5ad) ([merge request](gitlab-org/gitlab!78238)) **GitLab Enterprise Edition** +## 14.7.7 (2022-03-31) + +### Security (21 changes) + +- [Update to commonmarker 0.23.4](gitlab-org/security/gitlab@eb4b231173c86901f93b5b7781716b1f7706dad1) ([merge request](gitlab-org/security/gitlab!2283)) +- [Revert merge request approval groups behavior](gitlab-org/security/gitlab@08e3ecced649f6ad241db6de7050b1502f7bef21) ([merge request](gitlab-org/security/gitlab!2333)) +- [Disallow login if password matches a fixed list](gitlab-org/security/gitlab@02a69ab32da1ac67d855de3ee388d0bd2bb6586e) ([merge request](gitlab-org/security/gitlab!2359)) +- [Update devise-two-factor to 4.0.2](gitlab-org/security/gitlab@c9fde96c7780f5b883cd1ac63d7ac3d5f4d78dc6) ([merge request](gitlab-org/security/gitlab!2351)) +- [Limit the number of tags associated with a CI runner](gitlab-org/security/gitlab@00124d5f8ba0d7437d1f6f19b029754bf481185b) ([merge request](gitlab-org/security/gitlab!2305)) +- [GitLab Pages Security Updates for 14.9](gitlab-org/security/gitlab@d335917e233658fa9d4452053469c3582ef38368) ([merge request](gitlab-org/security/gitlab!2325)) +- [Upgrade swagger-ui dependency](gitlab-org/security/gitlab@7a8ce32f70fd0338817705651ee0dbe0a277d5f1) ([merge request](gitlab-org/security/gitlab!2338)) +- [Modify release link format check to avoid regex if string is too long](gitlab-org/security/gitlab@e18dc2be245bca7e192c8536d1ba7de2ad798c43) ([merge request](gitlab-org/security/gitlab!2244)) +- [Masks variables in error messages](gitlab-org/security/gitlab@1706c5cf9b939a6ab0682db7b8945feb851a3f8b) ([merge request](gitlab-org/security/gitlab!2292)) +- [Escape user provided string to prevent XSS](gitlab-org/security/gitlab@c57edf9ab52810d455e41d71bad4e4d12c098cad) ([merge request](gitlab-org/security/gitlab!2315)) +- [Monkey patch of RDoc to prevent Ruby segfault](gitlab-org/security/gitlab@f9e5597d1864d03bf1f0103787becbc84886968d) ([merge request](gitlab-org/security/gitlab!2233)) +- [Project import maps members' created_by_id users based on source user ID](gitlab-org/security/gitlab@3ea1e477e0596f15e040f42b59fa86953d057128) ([merge request](gitlab-org/security/gitlab!2239)) +- [Redact InvalidURIError error messages](gitlab-org/security/gitlab@a42ede835e32f44b68c1affe78a7ee48332bb30a) ([merge request](gitlab-org/security/gitlab!2297)) +- [Fix access for approval rules API](gitlab-org/security/gitlab@b8c3997763d1e041dc2b82e464a99a5b2f15a798) ([merge request](gitlab-org/security/gitlab!2324)) +- [Fix kroki exploit](gitlab-org/security/gitlab@ad123e33510103af4fb00378ef1fc8dae4cacb21) ([merge request](gitlab-org/security/gitlab!2278)) +- [Fix blind SSRF when looking up SSH host keys for mirroring](gitlab-org/security/gitlab@0209f44cb4876f0a9ef13d4c8875a95a0cda1e2f) ([merge request](gitlab-org/security/gitlab!2311)) +- [Escape original content in reference redactor](gitlab-org/security/gitlab@f63861d8fe7b2b8d161162063e7995782cbfada8) ([merge request](gitlab-org/security/gitlab!2319)) +- [Security fix for CI/CD analytics visibility](gitlab-org/security/gitlab@fea6a4ff80862f9dba493405d03d82cf129e8854) ([merge request](gitlab-org/security/gitlab!2274)) +- [Latest commit exposed through fork of a private project](gitlab-org/security/gitlab@b573cea38cdce020e5f25fb9de60e0e506c87a9b) ([merge request](gitlab-org/security/gitlab!2272)) +- [Fix Asana integration restricted branch filter](gitlab-org/security/gitlab@56e2d9ae3de4f587d2c8a5aa111c2922553d6b7b) ([merge request](gitlab-org/security/gitlab!2214)) +- [Revert "JH need more complex passwords"](gitlab-org/security/gitlab@2419522b02700ce98e0c4d6e7bfd4d28b6464506) ([merge request](gitlab-org/security/gitlab!2354)) + ## 14.7.6 (2022-03-24) ### Added (1 change) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 3ebf789f5a8..43c989b5531 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.56.0 +1.56.1 diff --git a/Gemfile b/Gemfile index f3283f49d3e..87798951691 100644 --- a/Gemfile +++ b/Gemfile @@ -67,7 +67,7 @@ gem 'akismet', '~> 3.0' gem 'invisible_captcha', '~> 1.1.0' # Two-factor authentication -gem 'devise-two-factor', '~> 4.0.0' +gem 'devise-two-factor', '~> 4.0.2' gem 'rqrcode-rails3', '~> 0.1.7' gem 'attr_encrypted', '~> 3.1.0' gem 'u2f', '~> 0.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 7fc00cd0004..344810f92a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -270,11 +270,11 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (4.0.0) - activesupport (< 6.2) + devise-two-factor (4.0.2) + activesupport (< 7.1) attr_encrypted (>= 1.3, < 4, != 2) devise (~> 4.0) - railties (< 6.2) + railties (< 7.1) rotp (~> 6.0) diff-lcs (1.4.4) diff_match_patch (0.1.0) @@ -1458,7 +1458,7 @@ DEPENDENCIES derailed_benchmarks device_detector devise (~> 4.7.2) - devise-two-factor (~> 4.0.0) + devise-two-factor (~> 4.0.2) diff_match_patch (~> 0.1.0) diffy (~> 3.3) discordrb-webhooks (~> 3.4) diff --git a/app/assets/javascripts/behaviors/components/diagram_performance_warning.vue b/app/assets/javascripts/behaviors/components/diagram_performance_warning.vue new file mode 100644 index 00000000000..31b2682b546 --- /dev/null +++ b/app/assets/javascripts/behaviors/components/diagram_performance_warning.vue @@ -0,0 +1,25 @@ + + + + + {{ $options.i18n.bodyText }} + + diff --git a/app/assets/javascripts/behaviors/markdown/constants.js b/app/assets/javascripts/behaviors/markdown/constants.js index b4545d6c6c6..13f8d9ef0cf 100644 --- a/app/assets/javascripts/behaviors/markdown/constants.js +++ b/app/assets/javascripts/behaviors/markdown/constants.js @@ -1,3 +1,19 @@ // https://prosemirror.net/docs/ref/#model.ParseRule.priority export const DEFAULT_PARSE_RULE_PRIORITY = 50; export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY; + +export const unrestrictedPages = [ + // Group wiki + 'groups:wikis:show', + 'groups:wikis:edit', + 'groups:wikis:create', + + // Project wiki + 'projects:wikis:show', + 'projects:wikis:edit', + 'projects:wikis:create', + + // Project files + 'projects:show', + 'projects:blob:show', +]; diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index 4bfce12c7c5..5079da9aa02 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import syntaxHighlight from '~/syntax_highlight'; import highlightCurrentUser from './highlight_current_user'; +import { renderKroki } from './render_kroki'; import renderMath from './render_math'; import renderMermaid from './render_mermaid'; import renderSandboxedMermaid from './render_sandboxed_mermaid'; @@ -12,6 +13,7 @@ import renderMetrics from './render_metrics'; // $.fn.renderGFM = function renderGFM() { syntaxHighlight(this.find('.js-syntax-highlight').get()); + renderKroki(this.find('.js-render-kroki[hidden]').get()); renderMath(this.find('.js-render-math')); if (gon.features?.sandboxedMermaid) { renderSandboxedMermaid(this.find('.js-render-mermaid')); diff --git a/app/assets/javascripts/behaviors/markdown/render_kroki.js b/app/assets/javascripts/behaviors/markdown/render_kroki.js new file mode 100644 index 00000000000..abe71694d73 --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/render_kroki.js @@ -0,0 +1,63 @@ +import Vue from 'vue'; +import DiagramPerformanceWarning from '../components/diagram_performance_warning.vue'; +import { unrestrictedPages } from './constants'; + +/** + * Create alert element. + * + * @param {Element} krokiImage Kroki `img` element + * @return {Element} Alert element + */ +function createAlert(krokiImage) { + const app = new Vue({ + el: document.createElement('div'), + name: 'DiagramPerformanceWarningRoot', + render(createElement) { + return createElement(DiagramPerformanceWarning, { + on: { + closeAlert() { + app.$destroy(); + app.$el.remove(); + }, + showImage() { + krokiImage.removeAttribute('hidden'); + app.$destroy(); + app.$el.remove(); + }, + }, + }); + }, + }); + + return app.$el; +} + +/** + * Add warning alert to hidden Kroki images, + * or show Kroki image if on an unrestricted page. + * + * Kroki images are given a hidden attribute by the + * backend when the original markdown source is large. + * + * @param {Array} krokiImages Array of hidden Kroki `img` elements + */ +export function renderKroki(krokiImages) { + const pageName = document.querySelector('body').dataset.page; + const isUnrestrictedPage = unrestrictedPages.includes(pageName); + + krokiImages.forEach((krokiImage) => { + if (isUnrestrictedPage) { + krokiImage.removeAttribute('hidden'); + return; + } + + const parent = krokiImage.parentElement; + + // A single Kroki image is processed multiple times for some reason, + // so this condition ensures we only create one alert per Kroki image + if (!parent.hasAttribute('data-kroki-processed')) { + parent.setAttribute('data-kroki-processed', 'true'); + parent.after(createAlert(krokiImage)); + } + }); +} diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index d78c456ed5b..f9cf3af98bb 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -3,6 +3,7 @@ import { once, countBy } from 'lodash'; import createFlash from '~/flash'; import { darkModeEnabled } from '~/lib/utils/color_utils'; import { __, sprintf } from '~/locale'; +import { unrestrictedPages } from './constants'; // Renders diagrams and flowcharts from text using Mermaid in any element with the // `js-render-mermaid` class. @@ -30,24 +31,6 @@ let renderedMermaidBlocks = 0; let mermaidModule = {}; -// Whitelist pages where we won't impose any restrictions -// on mermaid rendering -const WHITELISTED_PAGES = [ - // Group wiki - 'groups:wikis:show', - 'groups:wikis:edit', - 'groups:wikis:create', - - // Project wiki - 'projects:wikis:show', - 'projects:wikis:edit', - 'projects:wikis:create', - - // Project files - 'projects:show', - 'projects:blob:show', -]; - export function initMermaid(mermaid) { let theme = 'neutral'; @@ -163,7 +146,7 @@ function renderMermaids($els) { * up the entire thread and causing a DoS. */ if ( - !WHITELISTED_PAGES.includes(pageName) && + !unrestrictedPages.includes(pageName) && ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT || renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js index 85a991a1ec9..6922ec9c5a5 100644 --- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js @@ -9,6 +9,7 @@ import { } from '~/lib/utils/url_utility'; import { darkModeEnabled } from '~/lib/utils/color_utils'; import { setAttributes } from '~/lib/utils/dom_utils'; +import { unrestrictedPages } from './constants'; // Renders diagrams and flowcharts from text using Mermaid in any element with the // `js-render-mermaid` class. @@ -36,23 +37,6 @@ const BUFFER_IFRAME_HEIGHT = 10; const elsProcessingMap = new WeakMap(); let renderedMermaidBlocks = 0; -// Pages without any restrictions on mermaid rendering -const PAGES_WITHOUT_RESTRICTIONS = [ - // Group wiki - 'groups:wikis:show', - 'groups:wikis:edit', - 'groups:wikis:create', - - // Project wiki - 'projects:wikis:show', - 'projects:wikis:edit', - 'projects:wikis:create', - - // Project files - 'projects:show', - 'projects:blob:show', -]; - function shouldLazyLoadMermaidBlock(source) { /** * If source contains `&`, which means that it might @@ -149,7 +133,7 @@ function renderMermaids($els) { * up the entire thread and causing a DoS. */ if ( - !PAGES_WITHOUT_RESTRICTIONS.includes(pageName) && + !unrestrictedPages.includes(pageName) && ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT || renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js index b19cc19cb8c..a04da98ff77 100644 --- a/app/assets/javascripts/blob/openapi/index.js +++ b/app/assets/javascripts/blob/openapi/index.js @@ -1,6 +1,5 @@ import { SwaggerUIBundle } from 'swagger-ui-dist'; import createFlash from '~/flash'; -import { removeParams, updateHistory } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; export default () => { @@ -8,14 +7,10 @@ export default () => { Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')]) .then(() => { - // Temporary fix to prevent an XSS attack due to "useUnsafeMarkdown" - // Once we upgrade Swagger to "4.0.0", we can safely remove this as it will be deprecated - // Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/339696 - updateHistory({ url: removeParams(['useUnsafeMarkdown']), replace: true }); SwaggerUIBundle({ url: el.dataset.endpoint, dom_id: '#js-openapi-viewer', - useUnsafeMarkdown: false, + deepLinking: true, }); }) .catch((error) => { diff --git a/app/assets/javascripts/crm/components/contact_form.vue b/app/assets/javascripts/crm/components/contact_form.vue deleted file mode 100644 index 81ae5c246be..00000000000 --- a/app/assets/javascripts/crm/components/contact_form.vue +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - {{ title }} - - - - - {{ error }} - - - - - - - - - - - - - - - - - - - - - - {{ $options.i18n.cancel }} - - {{ buttonLabel }} - - - - diff --git a/app/assets/javascripts/crm/components/form.vue b/app/assets/javascripts/crm/components/form.vue index b24de1e95e8..4f94898ff63 100644 --- a/app/assets/javascripts/crm/components/form.vue +++ b/app/assets/javascripts/crm/components/form.vue @@ -61,11 +61,6 @@ export default { required: false, default: null, }, - existingModel: { - type: Object, - required: false, - default: () => ({}), - }, additionalCreateParams: { type: Object, required: false, @@ -76,25 +71,42 @@ export default { required: false, default: () => MSG_SAVE_CHANGES, }, + existingId: { + type: String, + required: false, + default: null, + }, }, data() { - const initialModel = this.fields.reduce( - (map, field) => - Object.assign(map, { - [field.name]: this.existingModel ? this.existingModel[field.name] : null, - }), - {}, - ); - return { - model: initialModel, + model: null, submitting: false, errorMessages: [], + records: [], + loading: true, }; }, + apollo: { + records: { + query() { + return this.getQuery.query; + }, + variables() { + return this.getQuery.variables; + }, + update(data) { + this.records = getPropValueByPath(data, this.getQueryNodePath).nodes || []; + this.setInitialModel(); + this.loading = false; + }, + error() { + this.errorMessages = [MSG_ERROR]; + }, + }, + }, computed: { isEditMode() { - return this.existingModel?.id; + return this.existingId; }, isInvalid() { const { fields, model } = this; @@ -115,13 +127,24 @@ export default { ); if (isEditMode) { - return { input: { id: this.existingModel.id, ...variables } }; + return { input: { id: this.existingId, ...variables } }; } return { input: { ...additionalCreateParams, ...variables } }; }, }, methods: { + setInitialModel() { + const existingModel = this.records.find(({ id }) => id === this.existingId); + + this.model = this.fields.reduce( + (map, field) => + Object.assign(map, { + [field.name]: !this.isEditMode || !existingModel ? null : existingModel[field.name], + }), + {}, + ); + }, formatValue(model, field) { if (!isEmpty(model[field.name]) && field.input?.type === 'number') { return parseFloat(model[field.name]); @@ -173,7 +196,7 @@ export default { const sourceData = store.readQuery(getQuery); const newData = produce(sourceData, (draftState) => { - getPropValueByPath(draftState, getQueryNodePath).nodes.push(getFirstPropertyValue(result)); + getPropValueByPath(draftState, getQueryNodePath).nodes.push(this.getPayload(result)); }); store.writeQuery({ @@ -185,6 +208,14 @@ export default { const optionalSuffix = field.required ? '' : ` ${MSG_OPTIONAL}`; return field.label + optionalSuffix; }, + getPayload(data) { + if (!data) return null; + + const keys = Object.keys(data); + if (keys[0] === '__typename') return data[keys[1]]; + + return data[keys[0]]; + }, }, MSG_CANCEL, INDEX_ROUTE_NAME, @@ -192,7 +223,7 @@ export default { - + {{ title }} diff --git a/app/assets/javascripts/crm/components/new_organization_form.vue b/app/assets/javascripts/crm/components/new_organization_form.vue deleted file mode 100644 index 3b11edc6935..00000000000 --- a/app/assets/javascripts/crm/components/new_organization_form.vue +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - {{ $options.i18n.title }} - - - - - {{ error }} - - - - - - - - - - - - - - - - {{ $options.i18n.cancel }} - - {{ $options.i18n.buttonLabel }} - - - - diff --git a/app/assets/javascripts/crm/contacts_bundle.js b/app/assets/javascripts/crm/contacts/bundle.js similarity index 100% rename from app/assets/javascripts/crm/contacts_bundle.js rename to app/assets/javascripts/crm/contacts/bundle.js diff --git a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue new file mode 100644 index 00000000000..58eaabfbb7f --- /dev/null +++ b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/app/assets/javascripts/crm/components/contacts_root.vue b/app/assets/javascripts/crm/contacts/components/contacts_root.vue similarity index 55% rename from app/assets/javascripts/crm/components/contacts_root.vue rename to app/assets/javascripts/crm/contacts/components/contacts_root.vue index 178ce84c64d..17be3800256 100644 --- a/app/assets/javascripts/crm/components/contacts_root.vue +++ b/app/assets/javascripts/crm/contacts/components/contacts_root.vue @@ -2,11 +2,9 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; import { parseBoolean } from '~/lib/utils/common_utils'; import { s__, __ } from '~/locale'; -import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_CRM_CONTACT } from '~/graphql_shared/constants'; -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; -import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; -import ContactForm from './contact_form.vue'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { EDIT_ROUTE_NAME, NEW_ROUTE_NAME } from '../../constants'; +import getGroupContactsQuery from './graphql/get_group_contacts.query.graphql'; export default { components: { @@ -14,12 +12,11 @@ export default { GlButton, GlLoadingIcon, GlTable, - ContactForm, }, directives: { GlTooltip: GlTooltipDirective, }, - inject: ['groupFullPath', 'groupIssuesPath', 'canAdminCrmContact'], + inject: ['canAdminCrmContact', 'groupFullPath', 'groupIssuesPath'], data() { return { contacts: [], @@ -48,50 +45,20 @@ export default { isLoading() { return this.$apollo.queries.contacts.loading; }, - showNewForm() { - return this.$route.name === NEW_ROUTE_NAME; - }, - showEditForm() { - return !this.isLoading && this.$route.name === EDIT_ROUTE_NAME; - }, canAdmin() { return parseBoolean(this.canAdminCrmContact); }, - editingContact() { - return this.contacts.find( - (contact) => contact.id === convertToGraphQLId(TYPE_CRM_CONTACT, this.$route.params.id), - ); - }, }, methods: { extractContacts(data) { const contacts = data?.group?.contacts?.nodes || []; return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName)); }, - displayNewForm() { - if (this.showNewForm) return; - - this.$router.push({ name: NEW_ROUTE_NAME }); - }, - hideNewForm(success) { - if (success) this.$toast.show(s__('Crm|Contact has been added')); - - this.$router.replace({ name: INDEX_ROUTE_NAME }); - }, - hideEditForm(success) { - if (success) this.$toast.show(s__('Crm|Contact has been updated')); - - this.editingContactId = 0; - this.$router.replace({ name: INDEX_ROUTE_NAME }); - }, getIssuesPath(path, value) { return `${path}?scope=all&state=opened&crm_contact_id=${value}`; }, - edit(value) { - if (this.showEditForm) return; - - this.editingContactId = value; - this.$router.push({ name: EDIT_ROUTE_NAME, params: { id: value } }); + getEditRoute(id) { + return { name: this.$options.EDIT_ROUTE_NAME, params: { id } }; }, }, fields: [ @@ -119,10 +86,12 @@ export default { emptyText: s__('Crm|No contacts found'), issuesButtonLabel: __('View issues'), editButtonLabel: __('Edit'), - title: s__('Crm|Customer Relations Contacts'), + title: s__('Crm|Customer relations contacts'), newContact: s__('Crm|New contact'), errorText: __('Something went wrong. Please try again.'), }, + EDIT_ROUTE_NAME, + NEW_ROUTE_NAME, }; @@ -137,24 +106,15 @@ export default { {{ $options.i18n.title }} - - - {{ $options.i18n.newContact }} - + + + + {{ $options.i18n.newContact }} + + - - + - + - + + + diff --git a/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql b/app/assets/javascripts/crm/contacts/components/graphql/create_contact.mutation.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql rename to app/assets/javascripts/crm/contacts/components/graphql/create_contact.mutation.graphql diff --git a/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql b/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql rename to app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql diff --git a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql b/app/assets/javascripts/crm/contacts/components/graphql/get_group_contacts.query.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql rename to app/assets/javascripts/crm/contacts/components/graphql/get_group_contacts.query.graphql diff --git a/app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql b/app/assets/javascripts/crm/contacts/components/graphql/update_contact.mutation.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql rename to app/assets/javascripts/crm/contacts/components/graphql/update_contact.mutation.graphql diff --git a/app/assets/javascripts/crm/routes.js b/app/assets/javascripts/crm/contacts/routes.js similarity index 56% rename from app/assets/javascripts/crm/routes.js rename to app/assets/javascripts/crm/contacts/routes.js index 12aa17d73b6..18768e1c775 100644 --- a/app/assets/javascripts/crm/routes.js +++ b/app/assets/javascripts/crm/contacts/routes.js @@ -1,4 +1,5 @@ -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from './constants'; +import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; +import ContactFormWrapper from './components/contact_form_wrapper.vue'; export default [ { @@ -8,9 +9,12 @@ export default [ { name: NEW_ROUTE_NAME, path: '/new', + component: ContactFormWrapper, }, { name: EDIT_ROUTE_NAME, path: '/:id/edit', + component: ContactFormWrapper, + props: { isEditMode: true }, }, ]; diff --git a/app/assets/javascripts/crm/organizations_bundle.js b/app/assets/javascripts/crm/organizations/bundle.js similarity index 100% rename from app/assets/javascripts/crm/organizations_bundle.js rename to app/assets/javascripts/crm/organizations/bundle.js diff --git a/app/assets/javascripts/crm/components/queries/create_organization.mutation.graphql b/app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/create_organization.mutation.graphql rename to app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql diff --git a/app/assets/javascripts/crm/components/queries/crm_organization_fields.fragment.graphql b/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/crm_organization_fields.fragment.graphql rename to app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql diff --git a/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql b/app/assets/javascripts/crm/organizations/components/graphql/get_group_organizations.query.graphql similarity index 100% rename from app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql rename to app/assets/javascripts/crm/organizations/components/graphql/get_group_organizations.query.graphql diff --git a/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql b/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql new file mode 100644 index 00000000000..a4c46d1f0fa --- /dev/null +++ b/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql @@ -0,0 +1,10 @@ +#import "./crm_organization_fields.fragment.graphql" + +mutation updateOrganization($input: CustomerRelationsOrganizationUpdateInput!) { + customerRelationsOrganizationUpdate(input: $input) { + organization { + ...OrganizationFragment + } + errors + } +} diff --git a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue new file mode 100644 index 00000000000..38468e1f4e4 --- /dev/null +++ b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/app/assets/javascripts/crm/components/organizations_root.vue b/app/assets/javascripts/crm/organizations/components/organizations_root.vue similarity index 72% rename from app/assets/javascripts/crm/components/organizations_root.vue rename to app/assets/javascripts/crm/organizations/components/organizations_root.vue index 9370c6377e9..522e29eb2af 100644 --- a/app/assets/javascripts/crm/components/organizations_root.vue +++ b/app/assets/javascripts/crm/organizations/components/organizations_root.vue @@ -3,9 +3,8 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@ import { parseBoolean } from '~/lib/utils/common_utils'; import { s__, __ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME } from '../constants'; -import getGroupOrganizationsQuery from './queries/get_group_organizations.query.graphql'; -import NewOrganizationForm from './new_organization_form.vue'; +import { EDIT_ROUTE_NAME, NEW_ROUTE_NAME } from '../../constants'; +import getGroupOrganizationsQuery from './graphql/get_group_organizations.query.graphql'; export default { components: { @@ -13,7 +12,6 @@ export default { GlButton, GlLoadingIcon, GlTable, - NewOrganizationForm, }, directives: { GlTooltip: GlTooltipDirective, @@ -21,8 +19,8 @@ export default { inject: ['canAdminCrmOrganization', 'groupFullPath', 'groupIssuesPath'], data() { return { - error: false, organizations: [], + error: false, }; }, apollo: { @@ -47,10 +45,7 @@ export default { isLoading() { return this.$apollo.queries.organizations.loading; }, - showNewForm() { - return this.$route.name === NEW_ROUTE_NAME; - }, - canCreateNew() { + canAdmin() { return parseBoolean(this.canAdminCrmOrganization); }, }, @@ -62,15 +57,8 @@ export default { getIssuesPath(path, value) { return `${path}?scope=all&state=opened&crm_organization_id=${value}`; }, - displayNewForm() { - if (this.showNewForm) return; - - this.$router.push({ name: NEW_ROUTE_NAME }); - }, - hideNewForm(success) { - if (success) this.$toast.show(this.$options.i18n.organizationAdded); - - this.$router.replace({ name: INDEX_ROUTE_NAME }); + getEditRoute(id) { + return { name: this.$options.EDIT_ROUTE_NAME, params: { id } }; }, }, fields: [ @@ -79,7 +67,7 @@ export default { { key: 'description', sortable: true }, { key: 'id', - label: __('Issues'), + label: '', formatter: (id) => { return getIdFromGraphQLId(id); }, @@ -88,11 +76,13 @@ export default { i18n: { emptyText: s__('Crm|No organizations found'), issuesButtonLabel: __('View issues'), - title: s__('Crm|Customer Relations Organizations'), + editButtonLabel: __('Edit'), + title: s__('Crm|Customer relations organizations'), newOrganization: s__('Crm|New organization'), errorText: __('Something went wrong. Please try again.'), - organizationAdded: s__('Crm|Organization has been added'), }, + EDIT_ROUTE_NAME, + NEW_ROUTE_NAME, }; @@ -108,15 +98,17 @@ export default { {{ $options.i18n.title }} - - {{ $options.i18n.newOrganization }} - + + + {{ $options.i18n.newOrganization }} + + - + - + + + + diff --git a/app/assets/javascripts/crm/organizations/routes.js b/app/assets/javascripts/crm/organizations/routes.js new file mode 100644 index 00000000000..85bd3b32877 --- /dev/null +++ b/app/assets/javascripts/crm/organizations/routes.js @@ -0,0 +1,20 @@ +import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; +import OrganizationFormWrapper from './components/organization_form_wrapper.vue'; + +export default [ + { + name: INDEX_ROUTE_NAME, + path: '/', + }, + { + name: NEW_ROUTE_NAME, + path: '/new', + component: OrganizationFormWrapper, + }, + { + name: EDIT_ROUTE_NAME, + path: '/:id/edit', + component: OrganizationFormWrapper, + props: { isEditMode: true }, + }, +]; diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index 3726743c032..22fa2912881 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -3,6 +3,7 @@ export const MINIMUM_SEARCH_LENGTH = 3; export const TYPE_BOARD = 'Board'; export const TYPE_CI_RUNNER = 'Ci::Runner'; export const TYPE_CRM_CONTACT = 'CustomerRelations::Contact'; +export const TYPE_CRM_ORGANIZATION = 'CustomerRelations::Organization'; export const TYPE_DISCUSSION = 'Discussion'; export const TYPE_EPIC = 'Epic'; export const TYPE_EPIC_BOARD = 'Boards::EpicBoard'; diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index ac2eb34260c..18eca11ac42 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -9,7 +9,7 @@ const LINK_TAG_PATTERN = '[{text}](url)'; // a bullet point character (*+-) and an optional checkbox ([ ] [x]) // OR a number with a . after it and an optional checkbox ([ ] [x]) // followed by one or more whitespace characters -const LIST_LINE_HEAD_PATTERN = /^(?\s*)(?((?[*+-])|(?\d+\.))( \[([x ])\])?\s)(?.)?/; +const LIST_LINE_HEAD_PATTERN = /^(?\s*)(?((?[*+-])|(?\d+\.))( \[([xX ])\])?\s)(?.)?/; function selectedText(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); @@ -391,6 +391,8 @@ function handleContinueList(e, textArea) { itemToInsert = `${indent}${leader}`; } + itemToInsert = itemToInsert.replace(/\[x\]/i, '[ ]'); + e.preventDefault(); updateText({ diff --git a/app/assets/javascripts/pages/groups/crm/contacts/index.js b/app/assets/javascripts/pages/groups/crm/contacts/index.js index a595246957f..6af47621c1d 100644 --- a/app/assets/javascripts/pages/groups/crm/contacts/index.js +++ b/app/assets/javascripts/pages/groups/crm/contacts/index.js @@ -1,3 +1,3 @@ -import initCrmContactsApp from '~/crm/contacts_bundle'; +import initCrmContactsApp from '~/crm/contacts/bundle'; initCrmContactsApp(); diff --git a/app/assets/javascripts/pages/groups/crm/organizations/index.js b/app/assets/javascripts/pages/groups/crm/organizations/index.js index 16479b43d52..2ad0904688e 100644 --- a/app/assets/javascripts/pages/groups/crm/organizations/index.js +++ b/app/assets/javascripts/pages/groups/crm/organizations/index.js @@ -1,3 +1,3 @@ -import initCrmOrganizationsApp from '~/crm/organizations_bundle'; +import initCrmOrganizationsApp from '~/crm/organizations/bundle'; initCrmOrganizationsApp(); diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue index eb77babcc57..b25d92d049e 100644 --- a/app/assets/javascripts/runner/components/runner_jobs.vue +++ b/app/assets/javascripts/runner/components/runner_jobs.vue @@ -1,5 +1,5 @@