diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-23 03:09:41 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-23 03:09:41 +0300 |
commit | 74d4d931ace289a65c8d1e9e0641622c7568d4a0 (patch) | |
tree | b53c02cd011a1e9e0e4454bea0f205822f3c03df /app/assets/javascripts/content_editor | |
parent | c4a9ca5ffca17bd874dd77014e9f2d607c94b06c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
4 files changed, 59 insertions, 10 deletions
diff --git a/app/assets/javascripts/content_editor/constants/index.js b/app/assets/javascripts/content_editor/constants/index.js index a39a243ec6b..c7fe64a5fb6 100644 --- a/app/assets/javascripts/content_editor/constants/index.js +++ b/app/assets/javascripts/content_editor/constants/index.js @@ -58,3 +58,9 @@ export const EXTENSION_PRIORITY_LOWER = 75; */ export const EXTENSION_PRIORITY_DEFAULT = 100; export const EXTENSION_PRIORITY_HIGHEST = 200; + +/** + * See lib/gitlab/file_type_detection.rb + */ +export const SAFE_VIDEO_EXT = ['mp4', 'm4v', 'mov', 'webm', 'ogv']; +export const SAFE_AUDIO_EXT = ['mp3', 'oga', 'ogg', 'spx', 'wav']; diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index f9de71f601b..ef02bc872e2 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -1,4 +1,5 @@ import { Extension } from '@tiptap/core'; +import Audio from './audio'; import Blockquote from './blockquote'; import Bold from './bold'; import BulletList from './bullet_list'; @@ -25,12 +26,14 @@ import Table from './table'; import TableCell from './table_cell'; import TableHeader from './table_header'; import TableRow from './table_row'; +import Video from './video'; export default Extension.create({ addGlobalAttributes() { return [ { types: [ + Audio.name, Bold.name, Blockquote.name, BulletList.name, @@ -56,6 +59,7 @@ export default Extension.create({ TableCell.name, TableHeader.name, TableRow.name, + Video.name, ...HTMLNodes.map((htmlNode) => htmlNode.name), ], attributes: { diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 472a0a4815b..12de6c41f77 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -108,7 +108,10 @@ const defaultSerializerConfig = { }, nodes: { - [Audio.name]: renderPlayable, + [Audio.name]: preserveUnchanged({ + render: renderPlayable, + inline: true, + }), [Blockquote.name]: preserveUnchanged((state, node) => { if (node.attrs.multiline) { state.write('>>>'); @@ -220,7 +223,10 @@ const defaultSerializerConfig = { else renderBulletList(state, node); }), [Text.name]: defaultMarkdownSerializer.nodes.text, - [Video.name]: renderPlayable, + [Video.name]: preserveUnchanged({ + render: renderPlayable, + inline: true, + }), [WordBreak.name]: (state) => state.write('<wbr>'), ...HTMLNodes.reduce((serializers, htmlNode) => { return { diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js index 8a15633708f..e5b78fa3d9b 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -1,7 +1,10 @@ import { render } from '~/lib/gfm'; import { isValidAttribute } from '~/lib/dompurify'; +import { SAFE_AUDIO_EXT, SAFE_VIDEO_EXT } from '../constants'; import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter'; +const ALL_AUDIO_VIDEO_EXT = [...SAFE_AUDIO_EXT, ...SAFE_VIDEO_EXT]; + const wrappableTags = ['img', 'br', 'code', 'i', 'em', 'b', 'strong', 'a', 'strike', 's', 'del']; const isTaskItem = (hastNode) => { @@ -17,6 +20,26 @@ const getTableCellAttrs = (hastNode) => ({ rowspan: parseInt(hastNode.properties.rowSpan, 10) || 1, }); +const getMediaAttrs = (hastNode) => ({ + src: hastNode.properties.src, + canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.src, + isReference: hastNode.properties.isReference === 'true', + title: hastNode.properties.title, + alt: hastNode.properties.alt, +}); + +const isMediaTag = (hastNode) => hastNode.tagName === 'img' && Boolean(hastNode.properties); + +const extractMediaFileExtension = (url) => { + try { + const parsedUrl = new URL(url, window.location.origin); + + return /\.(\w+)$/.exec(parsedUrl.pathname)?.[1] ?? null; + } catch { + return null; + } +}; + const factorySpecs = { blockquote: { type: 'block', selector: 'blockquote' }, paragraph: { type: 'block', selector: 'p' }, @@ -121,16 +144,26 @@ const factorySpecs = { selector: 'pre', wrapInParagraph: true, }, + audio: { + type: 'inline', + selector: (hastNode) => + isMediaTag(hastNode) && + SAFE_AUDIO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)), + getAttrs: getMediaAttrs, + }, image: { type: 'inline', - selector: 'img', - getAttrs: (hastNode) => ({ - src: hastNode.properties.src, - canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.src, - isReference: hastNode.properties.isReference === 'true', - title: hastNode.properties.title, - alt: hastNode.properties.alt, - }), + selector: (hastNode) => + isMediaTag(hastNode) && + !ALL_AUDIO_VIDEO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)), + getAttrs: getMediaAttrs, + }, + video: { + type: 'inline', + selector: (hastNode) => + isMediaTag(hastNode) && + SAFE_VIDEO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)), + getAttrs: getMediaAttrs, }, hardBreak: { type: 'inline', |