diff options
Diffstat (limited to 'app')
15 files changed, 121 insertions, 72 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 36317444af9..72aae254584 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -152,7 +152,9 @@ export class CopyAsGFM { if (lineElements.length > 0) { for (let i = 0; i < lineElements.length; i += 1) { const lineElement = lineElements[i]; - codeElement.appendChild(lineElement); + const line = document.createElement('span'); + line.append(...lineElement.childNodes); + codeElement.appendChild(line); codeElement.appendChild(document.createTextNode('\n')); } } else { diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index af1af86d0c4..3a08e3e546f 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -243,7 +243,7 @@ export default { </div> </gl-popover> </template> - <user-access-role-badge v-if="group.permission" class="gl-mr-3"> + <user-access-role-badge v-if="group.permission" size="sm" class="gl-mr-3"> {{ group.permission }} </user-access-role-badge> <gl-label @@ -254,7 +254,7 @@ export default { size="sm" /> </div> - <div v-if="group.description" class="description"> + <div v-if="group.description" class="description gl-font-sm gl-mt-1"> <span v-safe-html:[$options.safeHtmlConfig]="group.description" :itemprop="microdata.descriptionItemprop" diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index d87190edfd2..55c5ef2ae80 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -68,7 +68,7 @@ export default { css-class="project-stars" icon-name="star" /> - <div v-if="isProject" class="last-updated"> + <div v-if="isProject" class="last-updated gl-font-sm"> <time-ago-tooltip :time="item.lastActivityAt" tooltip-placement="bottom" /> </div> </div> diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index 97a1cbda5d0..264dbff525b 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -79,7 +79,7 @@ export default { const urlHash = getLocationHash(); // If there is a code line hash in the URL we render with the simple viewer const useSimpleViewer = usePlain || urlHash?.startsWith('L') || !this.hasRichViewer; - this.initHighlightWorker(this.blobInfo); + this.initHighlightWorker(this.blobInfo, this.isUsingLfs); this.switchViewer(useSimpleViewer ? SIMPLE_BLOB_VIEWER : RICH_BLOB_VIEWER); // By default, if present, use the rich viewer to render }, error() { diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js index d434700b29f..016f7f9fe43 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/index.js +++ b/app/assets/javascripts/repository/components/blob_viewers/index.js @@ -1,4 +1,4 @@ -import { TEXT_FILE_TYPE, JSON_LANGUAGE } from '../../constants'; +import { TEXT_FILE_TYPE } from '../../constants'; export const viewers = { csv: () => import('./csv_viewer.vue'), @@ -17,12 +17,10 @@ export const viewers = { geo_json: () => import('./geo_json/geo_json_viewer.vue'), }; -export const loadViewer = (type, isUsingLfs, hljsWorkerEnabled, language) => { +export const loadViewer = (type, isUsingLfs, hljsWorkerEnabled) => { let viewer = viewers[type]; - if (hljsWorkerEnabled && language === JSON_LANGUAGE && type === TEXT_FILE_TYPE) { - // The New Source Viewer currently only supports JSON files. - // More language support will be added in: https://gitlab.com/gitlab-org/gitlab/-/issues/415753 + if (hljsWorkerEnabled && type === TEXT_FILE_TYPE) { viewer = () => import('~/vue_shared/components/source_viewer/source_viewer_new.vue'); } diff --git a/app/assets/javascripts/repository/mixins/highlight_mixin.js b/app/assets/javascripts/repository/mixins/highlight_mixin.js index 5b6f68681bb..fa4f0f48512 100644 --- a/app/assets/javascripts/repository/mixins/highlight_mixin.js +++ b/app/assets/javascripts/repository/mixins/highlight_mixin.js @@ -8,6 +8,8 @@ import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/h import LineHighlighter from '~/blob/line_highlighter'; import languageLoader from '~/content_editor/services/highlight_js_language_loader'; import Tracking from '~/tracking'; +import axios from '~/lib/utils/axios_utils'; +import { TEXT_FILE_TYPE } from '../constants'; /* * This mixin is intended to be used as an interface between our highlight worker and Vue components @@ -36,14 +38,29 @@ export default { this.trackEvent(EVENT_LABEL_FALLBACK, language); this?.onError(); }, - initHighlightWorker({ rawTextBlob, language, fileType }) { - if (language !== 'json' || !this.glFeatures.highlightJsWorker) return; + async handleLFSBlob(externalStorageUrl, rawPath, language) { + await axios + .get(externalStorageUrl || rawPath) + .then(({ data }) => this.instructWorker(data, language)) + .catch(() => this.$emit('error')); + }, + initHighlightWorker(blob, isUsingLfs) { + const { rawTextBlob, language, fileType, externalStorageUrl, rawPath, simpleViewer } = blob; + + if (!this.glFeatures.highlightJsWorker || simpleViewer?.fileType !== TEXT_FILE_TYPE) return; if (this.isUnsupportedLanguage(language)) { this.handleUnsupportedLanguage(language); return; } + this.highlightWorker.onmessage = this.handleWorkerMessage; + + if (isUsingLfs) { + this.handleLFSBlob(externalStorageUrl, rawPath, language); + return; + } + /* * We want to start rendering content as soon as possible, but highlighting large amounts of * content can take long, so we render the content in phases: @@ -64,8 +81,6 @@ export default { this.chunks = splitIntoChunks(language, firstSeventyLines); - this.highlightWorker.onmessage = this.handleWorkerMessage; - // Instruct the worker to highlight the first 70 lines ASAP, this improves perceived performance. this.instructWorker(firstSeventyLines, language); diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js index 582093e5739..47b802d9d17 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js @@ -14,6 +14,7 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = { clean: 'clean', clojure: 'clojure', cmake: 'cmake', + codeowners: 'codeowners', coffeescript: 'coffeescript', coq: 'coq', cpp: 'cpp', diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue index dcefa66c403..7ced12952dd 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_new.vue @@ -5,7 +5,7 @@ import SafeHtml from '~/vue_shared/directives/safe_html'; import Tracking from '~/tracking'; import addBlobLinksTracking from '~/blob/blob_links_tracking'; import LineHighlighter from '~/blob/line_highlighter'; -import { EVENT_ACTION, EVENT_LABEL_VIEWER } from './constants'; +import { EVENT_ACTION, EVENT_LABEL_VIEWER, CODEOWNERS_FILE_NAME } from './constants'; import Chunk from './components/chunk_new.vue'; import Blame from './components/blame_info.vue'; import { calculateBlameOffset, shouldRender, toggleBlameClasses } from './utils'; @@ -21,6 +21,7 @@ export default { components: { Chunk, Blame, + CodeownersValidation: () => import('ee_component/blob/components/codeowners_validation.vue'), }, directives: { SafeHtml, @@ -45,6 +46,10 @@ export default { type: String, required: true, }, + currentRef: { + type: String, + required: true, + }, }, data() { return { @@ -66,6 +71,9 @@ export default { return result; }, []); }, + isCodeownersFile() { + return this.blob.name === CODEOWNERS_FILE_NAME; + }, }, watch: { showBlame: { @@ -136,11 +144,18 @@ export default { <blame v-if="showBlame && blameInfo.length" :blame-info="blameInfo" /> <div - class="file-content code js-syntax-highlight blob-content gl-display-flex gl-flex-direction-column gl-overflow-auto gl-w-full" + class="file-content code js-syntax-highlight blob-content gl-display-flex gl-flex-direction-column gl-overflow-auto gl-w-full blob-viewer" :class="$options.userColorScheme" data-type="simple" :data-path="blob.path" > + <codeowners-validation + v-if="isCodeownersFile" + class="gl-text-black-normal" + :current-ref="currentRef" + :project-path="projectPath" + :file-path="blob.path" + /> <chunk v-for="(chunk, index) in chunks" :key="index" diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js index 8d8e945cd5f..057a1c2d113 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js @@ -1,13 +1,35 @@ import hljs from 'highlight.js/lib/core'; -import json from 'highlight.js/lib/languages/json'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; import { registerPlugins } from '../plugins/index'; import { LINES_PER_CHUNK, NEWLINE, ROUGE_TO_HLJS_LANGUAGE_MAP } from '../constants'; -const initHighlightJs = (fileType, content, language) => { - // The Highlight Worker is currently scoped to JSON files. - // See the following issue for more: https://gitlab.com/gitlab-org/gitlab/-/issues/415753 - hljs.registerLanguage(language, json); +const loadLanguage = async (language) => { + const languageDefinition = await languageLoader[language](); + hljs.registerLanguage(language, languageDefinition.default); +}; + +const loadSubLanguages = async (languageDefinition) => { + // Some files can contain sub-languages (i.e., Svelte); this ensures that sub-languages are also loaded + if (!languageDefinition?.contains) return; + + // generate list of languages to load + const languages = new Set( + languageDefinition.contains + .filter((component) => Boolean(component.subLanguage)) + .map((component) => component.subLanguage), + ); + + if (languageDefinition.subLanguage) { + languages.add(languageDefinition.subLanguage); + } + + await Promise.all([...languages].map(loadLanguage)); +}; + +const initHighlightJs = async (fileType, content, language) => { registerPlugins(hljs, fileType, content, true); + await loadLanguage(language); + await loadSubLanguages(hljs.getLanguage(language)); }; const splitByLineBreaks = (content = '') => content.split(/\r?\n/); @@ -35,12 +57,12 @@ const splitIntoChunks = (language, rawContent, highlightedContent) => { return result; }; -const highlight = (fileType, rawContent, lang) => { +const highlight = async (fileType, rawContent, lang) => { const language = ROUGE_TO_HLJS_LANGUAGE_MAP[lang.toLowerCase()]; let result; if (language) { - initHighlightJs(fileType, rawContent, language); + await initHighlightJs(fileType, rawContent, language); const highlightedContent = hljs.highlight(rawContent, { language }).value; result = splitIntoChunks(language, rawContent, highlightedContent); } diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_worker.js b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_worker.js index 535e857d7a9..49afaba3d2f 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_worker.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_worker.js @@ -4,7 +4,7 @@ import { highlight } from './highlight_utils'; * A webworker for highlighting large amounts of content with Highlight.js */ // eslint-disable-next-line no-restricted-globals -self.addEventListener('message', ({ data: { fileType, content, language } }) => { +self.addEventListener('message', async ({ data: { fileType, content, language } }) => { // eslint-disable-next-line no-restricted-globals - self.postMessage(highlight(fileType, content, language)); + self.postMessage(await highlight(fileType, content, language)); }); diff --git a/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue b/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue index e5558c038b3..43e35f2b1f0 100644 --- a/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue +++ b/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue @@ -12,11 +12,18 @@ export default { components: { GlBadge, }, + props: { + size: { + type: String, + required: false, + default: 'md', + }, + }, }; </script> <template> - <gl-badge class="gl-bg-transparent! gl-inset-border-1-gray-100!"> + <gl-badge :size="size" class="gl-bg-transparent! gl-inset-border-1-gray-100!"> <slot></slot> </gl-badge> </template> diff --git a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue index cbe7de4abcd..503328f7b03 100644 --- a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue +++ b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue @@ -58,11 +58,6 @@ export default { default: true, }, }, - data() { - return { - isFocused: false, - }; - }, computed: { labels() { return this.metadataWidgets[WIDGET_TYPE_LABELS]?.labels?.nodes || []; @@ -117,7 +112,7 @@ export default { return false; }, showRemove() { - return this.canUpdate && this.isFocused; + return this.canUpdate; }, displayLabels() { return this.showLabels && this.labels.length; @@ -135,10 +130,6 @@ export default { <div class="item-body work-item-link-child gl-relative gl-display-flex gl-flex-grow-1 gl-overflow-break-word gl-min-w-0 gl-pl-3 gl-pr-2 gl-py-2 gl-mx-n2 gl-rounded-base gl-gap-3" data-testid="links-child" - @mouseover="isFocused = true" - @mouseleave="isFocused = false" - @focusin="isFocused = true" - @focusout="isFocused = false" > <div class="item-contents gl-display-flex gl-flex-grow-1 gl-flex-wrap gl-min-w-0"> <div @@ -203,12 +194,14 @@ export default { </div> <div v-if="canUpdate"> <gl-button + v-gl-tooltip :class="{ 'gl-visibility-visible': showRemove }" class="gl-visibility-hidden" category="tertiary" size="small" icon="close" :aria-label="$options.i18n.remove" + :title="$options.i18n.remove" data-testid="remove-work-item-link" @click="$emit('removeChild', childItem)" /> diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue index 49454c3d9f3..f43718c4cb8 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue @@ -213,10 +213,10 @@ export default { </script> <template> - <li class="tree-item"> + <li class="tree-item gl-p-0! gl-border-bottom-0!"> <div class="gl-display-flex gl-align-items-flex-start" - :class="{ 'gl-ml-6': canHaveChildren && !hasChildren && hasIndirectChildren }" + :class="{ 'gl-ml-5 gl-pl-2': canHaveChildren && !hasChildren && hasIndirectChildren }" > <gl-button v-if="hasChildren" @@ -227,7 +227,7 @@ export default { category="tertiary" size="small" :loading="isLoadingChildren" - class="gl-px-0! gl-py-3! gl-mr-3" + class="gl-px-0! gl-py-3! gl-mr-2" data-testid="expand-child" @click="toggleItem" /> diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue index 3d09a90169c..09d2e688174 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue @@ -146,39 +146,39 @@ export default { /> </template> <template #body> - <div v-if="!isShownAddForm && children.length === 0" data-testid="tree-empty"> - <div class="gl-new-card-content"> + <div class="gl-new-card-content"> + <div v-if="!isShownAddForm && children.length === 0" data-testid="tree-empty"> <p class="gl-new-card-empty"> {{ $options.WORK_ITEMS_TREE_TEXT_MAP[workItemType].empty }} </p> </div> + <work-item-links-form + v-if="isShownAddForm" + ref="wiLinksForm" + data-testid="add-tree-form" + :full-path="fullPath" + :issuable-gid="workItemId" + :work-item-iid="workItemIid" + :form-type="formType" + :parent-work-item-type="parentWorkItemType" + :children-type="childType" + :children-ids="childrenIds" + :parent-confidential="confidential" + @cancel="hideAddForm" + @addChild="$emit('addChild')" + /> + <work-item-children-wrapper + :children="children" + :can-update="canUpdate" + :full-path="fullPath" + :work-item-id="workItemId" + :work-item-iid="workItemIid" + :work-item-type="workItemType" + :show-labels="showLabels" + @error="error = $event" + @show-modal="showModal" + /> </div> - <work-item-links-form - v-if="isShownAddForm" - ref="wiLinksForm" - data-testid="add-tree-form" - :full-path="fullPath" - :issuable-gid="workItemId" - :work-item-iid="workItemIid" - :form-type="formType" - :parent-work-item-type="parentWorkItemType" - :children-type="childType" - :children-ids="childrenIds" - :parent-confidential="confidential" - @cancel="hideAddForm" - @addChild="$emit('addChild')" - /> - <work-item-children-wrapper - :children="children" - :can-update="canUpdate" - :full-path="fullPath" - :work-item-id="workItemId" - :work-item-iid="workItemIid" - :work-item-type="workItemType" - :show-labels="showLabels" - @error="error = $event" - @show-modal="showModal" - /> </template> </widget-wrapper> </template> diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index f39cc1a8534..d27328f89cd 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -4,11 +4,7 @@ require 'prometheus/client/formats/text' class MetricsService def prometheus_metrics_text - if Feature.enabled?(:prom_metrics_rust) - ::Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path, use_rust: true) - else - ::Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path) - end + ::Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path) end def metrics_text |