diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-28 06:10:17 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-28 06:10:17 +0000 |
commit | cf73ed7d11fdf517c230038fe8c88c96b7e3d6f0 (patch) | |
tree | 2bff9cfe778217ae462963ff4c22bb80bd274969 /app | |
parent | c01895daab6ec8bffc5466a8330fc154fd47a726 (diff) | |
download | gitlab-ce-cf73ed7d11fdf517c230038fe8c88c96b7e3d6f0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
9 files changed, 130 insertions, 34 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/playable.js b/app/assets/javascripts/behaviors/markdown/nodes/playable.js index 33bb6e0c31c..64bc30e1fe7 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/playable.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/playable.js @@ -1,9 +1,7 @@ /* eslint-disable class-methods-use-this */ -/* eslint-disable @gitlab/require-i18n-strings */ import { defaultMarkdownSerializer } from 'prosemirror-markdown'; import { Node } from 'tiptap'; -import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; /** * Abstract base class for playable media, like video and audio. @@ -32,34 +30,34 @@ export default class Playable extends Node { const parseDOM = [ { - tag: `.${this.mediaType}-container`, - skip: true, - }, - { - tag: `.${this.mediaType}-container p`, - priority: HIGHER_PARSE_RULE_PRIORITY, - ignore: true, - }, - { - tag: `${this.mediaType}[src]`, - getAttrs: (el) => ({ src: el.src, alt: el.dataset.title }), + tag: `.media-container`, + getAttrs: (el) => ({ + src: el.querySelector('audio,video').src, + alt: el.querySelector('audio,video').dataset.title, + }), }, ]; const toDOM = (node) => [ - this.mediaType, - { - src: node.attrs.src, - controls: true, - 'data-setup': '{}', - 'data-title': node.attrs.alt, - ...this.extraElementAttrs, - }, + 'span', + { class: 'media-container' }, + [ + this.options.mediaType, + { + src: node.attrs.src, + controls: true, + 'data-setup': '{}', + 'data-title': node.attrs.alt, + ...this.extraElementAttrs, + }, + ], + ['a', { href: node.attrs.src }, node.attrs.alt], ]; return { attrs, - group: 'block', + group: 'inline', + inline: true, draggable: true, parseDOM, toDOM, @@ -68,6 +66,5 @@ export default class Playable extends Node { toMarkdown(state, node) { defaultMarkdownSerializer.nodes.image(state, node); - state.closeBlock(node); } } diff --git a/app/assets/javascripts/content_editor/extensions/audio.js b/app/assets/javascripts/content_editor/extensions/audio.js new file mode 100644 index 00000000000..3e746a632df --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/audio.js @@ -0,0 +1,8 @@ +import Playable from './playable'; + +export default Playable.extend({ + defaultOptions: { + ...Playable.options, + mediaType: 'audio', + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/playable.js b/app/assets/javascripts/content_editor/extensions/playable.js new file mode 100644 index 00000000000..fdc8f173c81 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/playable.js @@ -0,0 +1,71 @@ +import { Node } from '@tiptap/core'; + +const queryPlayableElement = (element, mediaType) => element.querySelector(mediaType); + +export default Node.create({ + name: 'playable', + group: 'inline', + inline: true, + draggable: true, + + addAttributes() { + return { + src: { + default: null, + parseHTML: (element) => { + const playable = queryPlayableElement(element, this.options.mediaType); + + return { + src: playable.src, + }; + }, + }, + canonicalSrc: { + default: null, + parseHTML: (element) => { + const playable = queryPlayableElement(element, this.options.mediaType); + + return { + canonicalSrc: playable.dataset.canonicalSrc, + }; + }, + }, + alt: { + default: null, + parseHTML: (element) => { + const playable = queryPlayableElement(element, this.options.mediaType); + + return { + alt: playable.dataset.title, + }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: '.media-container', + }, + ]; + }, + + renderHTML({ node }) { + return [ + 'span', + { class: 'media-container' }, + [ + this.options.mediaType, + { + src: node.attrs.src, + controls: true, + 'data-setup': '{}', + 'data-title': node.attrs.alt, + ...this.extraElementAttrs, + }, + ], + ['a', { href: node.attrs.src }, node.attrs.alt], + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index 67d5a00b6c6..f6aa1a1150d 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -2,6 +2,7 @@ import { Editor } from '@tiptap/vue-2'; import { isFunction } from 'lodash'; import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '../constants'; import Attachment from '../extensions/attachment'; +import Audio from '../extensions/audio'; import Blockquote from '../extensions/blockquote'; import Bold from '../extensions/bold'; import BulletList from '../extensions/bullet_list'; @@ -63,6 +64,7 @@ export const createContentEditor = ({ const builtInContentEditorExtensions = [ Attachment.configure({ uploadsPath, renderMarkdown }), + Audio, Blockquote, Bold, BulletList, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index aca8c2c8488..c49580ee91f 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -3,6 +3,7 @@ import { defaultMarkdownSerializer, } from 'prosemirror-markdown/src/to_markdown'; import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; +import Audio from '../extensions/audio'; import Blockquote from '../extensions/blockquote'; import Bold from '../extensions/bold'; import BulletList from '../extensions/bullet_list'; @@ -40,6 +41,8 @@ import { openTag, closeTag, renderOrderedList, + renderImage, + renderPlayable, } from './serialization_helpers'; const defaultSerializerConfig = { @@ -92,6 +95,7 @@ const defaultSerializerConfig = { }, nodes: { + [Audio.name]: renderPlayable, [Blockquote.name]: (state, node) => { if (node.attrs.multiline) { state.write('>>>'); @@ -120,12 +124,7 @@ const defaultSerializerConfig = { [HardBreak.name]: renderHardBreak, [Heading.name]: defaultMarkdownSerializer.nodes.heading, [HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule, - [Image.name]: (state, node) => { - const { alt, canonicalSrc, src, title } = node.attrs; - const quotedTitle = title ? ` ${state.quote(title)}` : ''; - - state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`); - }, + [Image.name]: renderImage, [ListItem.name]: defaultMarkdownSerializer.nodes.list_item, [OrderedList.name]: renderOrderedList, [Paragraph.name]: defaultMarkdownSerializer.nodes.paragraph, diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 54c51703b59..a45f76e8d47 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -286,3 +286,14 @@ export function renderHardBreak(state, node, parent, index) { } } } + +export function renderImage(state, node) { + const { alt, canonicalSrc, src, title } = node.attrs; + const quotedTitle = title ? ` ${state.quote(title)}` : ''; + + state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`); +} + +export function renderPlayable(state, node) { + renderImage(state, node); +} diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 00a471497e6..aeb3bb2286f 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -41,6 +41,12 @@ } } + .media-container { + display: inline-flex; + flex-direction: column; + margin-bottom: $gl-spacing-scale-2; + } + img:not(.emoji) { margin: 0 0 8px; } diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 8dd650751bf..0935db279df 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -58,8 +58,8 @@ class CommitStatus < Ci::ApplicationRecord scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) } scope :eager_load_pipeline, -> { eager_load(:pipeline, project: { namespace: :route }) } scope :with_pipeline, -> { joins(:pipeline) } - scope :updated_at_before, ->(date) { where('ci_builds.updated_at < ?', date) } scope :created_at_before, ->(date) { where('ci_builds.created_at < ?', date) } + scope :updated_at_before, ->(date) { where('ci_builds.updated_at < ?', date) } scope :updated_before, ->(lookback:, timeout:) { where('(ci_builds.created_at BETWEEN ? AND ?) AND (ci_builds.updated_at BETWEEN ? AND ?)', lookback, timeout, lookback, timeout) } diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index 7754f0d2d27..ddc6e17583f 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -63,10 +63,12 @@ class StuckCiJobsWorker # rubocop:disable Scalability/IdempotentWorker end def running_timed_out_builds - Ci::Build.running.where( # rubocop: disable CodeReuse/ActiveRecord - 'ci_builds.updated_at < ?', - BUILD_RUNNING_OUTDATED_TIMEOUT.ago - ) + if Feature.enabled?(:ci_new_query_for_running_stuck_jobs) + running_builds = Ci::Build.running.created_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago).order(created_at: :asc, project_id: :asc) # rubocop: disable CodeReuse/ActiveRecord + Ci::Build.id_in(running_builds).updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago) + else + Ci::Build.running.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago) + end end def try_obtain_lease |