diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index b76d5a2f675..40a59eaabe4 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -805,29 +805,29 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/user/workspace/index.md @fneill [Authentication and Authorization] -app/**/*password* @gitlab-org/manage/authentication-and-authorization -ee/app/**/*password* @gitlab-org/manage/authentication-and-authorization -config/**/*password* @gitlab-org/manage/authentication-and-authorization -ee/config/**/*password* @gitlab-org/manage/authentication-and-authorization -lib/**/*password* @gitlab-org/manage/authentication-and-authorization -ee/lib/**/*password* @gitlab-org/manage/authentication-and-authorization -app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization -ee/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization +/app/**/*password* @gitlab-org/manage/authentication-and-authorization +/ee/app/**/*password* @gitlab-org/manage/authentication-and-authorization +/config/**/*password* @gitlab-org/manage/authentication-and-authorization +/ee/config/**/*password* @gitlab-org/manage/authentication-and-authorization +/lib/**/*password* @gitlab-org/manage/authentication-and-authorization +/ee/lib/**/*password* @gitlab-org/manage/authentication-and-authorization +/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization +/ee/app/controllers/**/*password* @gitlab-org/manage/authentication-and-authorization -app/**/*auth* @gitlab-org/manage/authentication-and-authorization -ee/app/**/*auth* @gitlab-org/manage/authentication-and-authorization -config/**/*auth* @gitlab-org/manage/authentication-and-authorization -ee/config/**/*auth* @gitlab-org/manage/authentication-and-authorization -lib/**/*auth* @gitlab-org/manage/authentication-and-authorization -ee/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization -app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization -ee/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization +/app/**/*auth* @gitlab-org/manage/authentication-and-authorization +/ee/app/**/*auth* @gitlab-org/manage/authentication-and-authorization +/config/**/*auth* @gitlab-org/manage/authentication-and-authorization +/ee/config/**/*auth* @gitlab-org/manage/authentication-and-authorization +/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization +/ee/lib/**/*auth* @gitlab-org/manage/authentication-and-authorization +/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization +/ee/app/controllers/**/*auth* @gitlab-org/manage/authentication-and-authorization -app/**/*token* @gitlab-org/manage/authentication-and-authorization -ee/app/**/*token* @gitlab-org/manage/authentication-and-authorization -config/**/*token* @gitlab-org/manage/authentication-and-authorization -ee/config/**/*token* @gitlab-org/manage/authentication-and-authorization -lib/**/*token* @gitlab-org/manage/authentication-and-authorization -ee/lib/**/*token* @gitlab-org/manage/authentication-and-authorization -app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization -ee/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization +/app/**/*token* @gitlab-org/manage/authentication-and-authorization +/ee/app/**/*token* @gitlab-org/manage/authentication-and-authorization +/config/**/*token* @gitlab-org/manage/authentication-and-authorization +/ee/config/**/*token* @gitlab-org/manage/authentication-and-authorization +/lib/**/*token* @gitlab-org/manage/authentication-and-authorization +/ee/lib/**/*token* @gitlab-org/manage/authentication-and-authorization +/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization +/ee/app/controllers/**/*token* @gitlab-org/manage/authentication-and-authorization diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 44a5e718d50..3b112e6c2c1 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -14.9.0 +14.10.0-rc2 diff --git a/app/assets/javascripts/code_navigation/components/app.vue b/app/assets/javascripts/code_navigation/components/app.vue index d65b9a71288..81edbb4182e 100644 --- a/app/assets/javascripts/code_navigation/components/app.vue +++ b/app/assets/javascripts/code_navigation/components/app.vue @@ -23,6 +23,11 @@ export default { required: false, default: null, }, + wrapTextNodes: { + type: Boolean, + required: false, + default: false, + }, }, computed: { ...mapState([ @@ -37,6 +42,7 @@ export default { const initialData = { blobs: [{ path: this.blobPath, codeNavigationPath: this.codeNavigationPath }], definitionPathPrefix: this.pathPrefix, + wrapTextNodes: this.wrapTextNodes, }; this.setInitialData(initialData); } diff --git a/app/assets/javascripts/code_navigation/store/actions.js b/app/assets/javascripts/code_navigation/store/actions.js index 0b6b8437db5..562b78a891a 100644 --- a/app/assets/javascripts/code_navigation/store/actions.js +++ b/app/assets/javascripts/code_navigation/store/actions.js @@ -22,7 +22,7 @@ export default { ...d, definitionLineNumber: parseInt(d.definition_path?.split('#L').pop() || 0, 10), }; - addInteractionClass(path, d); + addInteractionClass({ path, d, wrapTextNodes: state.wrapTextNodes }); } return acc; }, {}); @@ -34,7 +34,9 @@ export default { }, showBlobInteractionZones({ state }, path) { if (state.data && state.data[path]) { - Object.values(state.data[path]).forEach((d) => addInteractionClass(path, d)); + Object.values(state.data[path]).forEach((d) => + addInteractionClass({ path, d, wrapTextNodes: state.wrapTextNodes }), + ); } }, showDefinition({ commit, state }, { target: el }) { diff --git a/app/assets/javascripts/code_navigation/store/mutations.js b/app/assets/javascripts/code_navigation/store/mutations.js index 07b190c7476..98beffe231c 100644 --- a/app/assets/javascripts/code_navigation/store/mutations.js +++ b/app/assets/javascripts/code_navigation/store/mutations.js @@ -1,9 +1,10 @@ import * as types from './mutation_types'; export default { - [types.SET_INITIAL_DATA](state, { blobs, definitionPathPrefix }) { + [types.SET_INITIAL_DATA](state, { blobs, definitionPathPrefix, wrapTextNodes }) { state.blobs = blobs; state.definitionPathPrefix = definitionPathPrefix; + state.wrapTextNodes = wrapTextNodes; }, [types.REQUEST_DATA](state) { state.loading = true; diff --git a/app/assets/javascripts/code_navigation/store/state.js b/app/assets/javascripts/code_navigation/store/state.js index 569d2f7b319..17505b8392c 100644 --- a/app/assets/javascripts/code_navigation/store/state.js +++ b/app/assets/javascripts/code_navigation/store/state.js @@ -2,6 +2,7 @@ export default () => ({ blobs: [], loading: false, data: null, + wrapTextNodes: false, currentDefinition: null, currentDefinitionPosition: null, currentBlobPath: null, diff --git a/app/assets/javascripts/code_navigation/utils/dom_utils.js b/app/assets/javascripts/code_navigation/utils/dom_utils.js new file mode 100644 index 00000000000..1a65c1a64a2 --- /dev/null +++ b/app/assets/javascripts/code_navigation/utils/dom_utils.js @@ -0,0 +1,31 @@ +const TEXT_NODE = 3; + +const isTextNode = ({ nodeType }) => nodeType === TEXT_NODE; + +const isBlank = (str) => !str || /^\s*$/.test(str); + +const isMatch = (s1, s2) => !isBlank(s1) && s1.trim() === s2.trim(); + +const createSpan = (content) => { + const span = document.createElement('span'); + span.innerText = content; + return span; +}; + +const wrapSpacesWithSpans = (text) => text.replace(/ /g, createSpan(' ').outerHTML); + +const wrapTextWithSpan = (el, text) => { + if (isTextNode(el) && isMatch(el.textContent, text)) { + const newEl = createSpan(text.trim()); + el.replaceWith(newEl); + } +}; + +const wrapNodes = (text) => { + const wrapper = createSpan(); + wrapper.innerHTML = wrapSpacesWithSpans(text); + wrapper.childNodes.forEach((el) => wrapTextWithSpan(el, text)); + return wrapper.childNodes; +}; + +export { wrapNodes, isTextNode }; diff --git a/app/assets/javascripts/code_navigation/utils/index.js b/app/assets/javascripts/code_navigation/utils/index.js index 6c078891ed4..0d72153d8fe 100644 --- a/app/assets/javascripts/code_navigation/utils/index.js +++ b/app/assets/javascripts/code_navigation/utils/index.js @@ -1,9 +1,11 @@ +import { wrapNodes, isTextNode } from './dom_utils'; + export const cachedData = new Map(); export const getCurrentHoverElement = () => cachedData.get('current'); export const setCurrentHoverElement = (el) => cachedData.set('current', el); -export const addInteractionClass = (path, d) => { +export const addInteractionClass = ({ path, d, wrapTextNodes }) => { const lineNumber = d.start_line + 1; const lines = document .querySelector(`[data-path="${path}"]`) @@ -12,13 +14,24 @@ export const addInteractionClass = (path, d) => { lines.forEach((line) => { let charCount = 0; + + if (wrapTextNodes) { + line.childNodes.forEach((elm) => { + if (isTextNode(elm)) { + // Highlight.js does not wrap all text nodes by default + // We need all text nodes to be wrapped in order to append code nav attributes + elm.replaceWith(...wrapNodes(elm.textContent)); + } + }); + } + const el = [...line.childNodes].find(({ textContent }) => { if (charCount === d.start_char) return true; charCount += textContent.length; return false; }); - if (el) { + if (el && !isTextNode(el)) { el.setAttribute('data-char-index', d.start_char); el.setAttribute('data-line-index', d.start_line); el.classList.add('cursor-pointer', 'code-navigation', 'js-code-navigation'); diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue index b55cd406adc..9e0c31e29e5 100644 --- a/app/assets/javascripts/incidents/components/incidents_list.vue +++ b/app/assets/javascripts/incidents/components/incidents_list.vue @@ -358,7 +358,7 @@ export default { :loading="redirecting" :disabled="redirecting" category="primary" - variant="success" + variant="confirm" :href="newIncidentPath" @click="navigateToCreateNewIncident" > diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue index 753a15871ab..f16e0287d5d 100644 --- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue +++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue @@ -171,6 +171,7 @@ export default { data-testid="cancel-button" icon="cancel" :title="$options.CANCEL" + :aria-label="$options.CANCEL" :disabled="cancelBtnDisabled" @click="cancelJob()" /> @@ -182,6 +183,7 @@ export default { v-gl-modal-directive="$options.playJobModalId" icon="play" :title="$options.ACTIONS_START_NOW" + :aria-label="$options.ACTIONS_START_NOW" data-testid="play-scheduled" /> {{ $options.i18n.newRelease }} diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index 85652301f4d..7aadc00ccea 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -301,6 +301,7 @@ export default { :code-navigation-path="blobInfo.codeNavigationPath" :blob-path="blobInfo.path" :path-prefix="blobInfo.projectBlobPathRoot" + :wrap-text-nodes="glFeatures.highlightJs" /> diff --git a/app/assets/javascripts/user_lists/components/user_list_form.vue b/app/assets/javascripts/user_lists/components/user_list_form.vue index b53aaf46ace..44aa2d9a5b4 100644 --- a/app/assets/javascripts/user_lists/components/user_list_form.vue +++ b/app/assets/javascripts/user_lists/components/user_list_form.vue @@ -84,7 +84,7 @@ export default {
- + {{ saveButtonLabel }} diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue index 15411e8d17e..edf2229a9a1 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -1,6 +1,7 @@