Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
21db5294d4
commit
acb2f0ab94
|
@ -38,7 +38,8 @@ AllCops:
|
|||
- 'workhorse/**/*'
|
||||
- 'spec/support/*.git/**/*' # e.g. spec/support/gitlab-git-test.git
|
||||
- 'db/ci_migrate/*.rb' # since the `db/ci_migrate` is a symlinked to `db/migrate`
|
||||
CacheRootDirectory: tmp
|
||||
# Use absolute path to avoid orphan directories with changed workspace root.
|
||||
CacheRootDirectory: <%= Dir.getwd %>/tmp
|
||||
MaxFilesInCache: 25000
|
||||
|
||||
Cop/AvoidKeywordArgumentsInSidekiqWorkers:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TableCell } from '@tiptap/extension-table-cell';
|
||||
import { isBlockTablesFeatureEnabled } from '../services/feature_flags';
|
||||
|
||||
export default TableCell.extend({
|
||||
content: 'inline*',
|
||||
content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*',
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TableHeader } from '@tiptap/extension-table-header';
|
||||
import { isBlockTablesFeatureEnabled } from '../services/feature_flags';
|
||||
|
||||
export default TableHeader.extend({
|
||||
content: 'inline*',
|
||||
content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function isBlockTablesFeatureEnabled() {
|
||||
return gon.features?.contentEditorBlockTables;
|
||||
}
|
|
@ -30,6 +30,12 @@ import TableRow from '../extensions/table_row';
|
|||
import TaskItem from '../extensions/task_item';
|
||||
import TaskList from '../extensions/task_list';
|
||||
import Text from '../extensions/text';
|
||||
import {
|
||||
renderHardBreak,
|
||||
renderTable,
|
||||
renderTableCell,
|
||||
renderTableRow,
|
||||
} from './serialization_helpers';
|
||||
|
||||
const defaultSerializerConfig = {
|
||||
marks: {
|
||||
|
@ -65,6 +71,7 @@ const defaultSerializerConfig = {
|
|||
expelEnclosingWhitespace: true,
|
||||
},
|
||||
},
|
||||
|
||||
nodes: {
|
||||
[Blockquote.name]: defaultMarkdownSerializer.nodes.blockquote,
|
||||
[BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list,
|
||||
|
@ -80,7 +87,7 @@ const defaultSerializerConfig = {
|
|||
|
||||
state.write(`:${name}:`);
|
||||
},
|
||||
[HardBreak.name]: defaultMarkdownSerializer.nodes.hard_break,
|
||||
[HardBreak.name]: renderHardBreak,
|
||||
[Heading.name]: defaultMarkdownSerializer.nodes.heading,
|
||||
[HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule,
|
||||
[Image.name]: (state, node) => {
|
||||
|
@ -95,60 +102,10 @@ const defaultSerializerConfig = {
|
|||
[Reference.name]: (state, node) => {
|
||||
state.write(node.attrs.originalText || node.attrs.text);
|
||||
},
|
||||
[Table.name]: (state, node) => {
|
||||
state.renderContent(node);
|
||||
},
|
||||
[TableCell.name]: (state, node) => {
|
||||
state.renderInline(node);
|
||||
},
|
||||
[TableHeader.name]: (state, node) => {
|
||||
state.renderInline(node);
|
||||
},
|
||||
[TableRow.name]: (state, node) => {
|
||||
const isHeaderRow = node.child(0).type.name === 'tableHeader';
|
||||
|
||||
const renderRow = () => {
|
||||
const cellWidths = [];
|
||||
|
||||
state.flushClose(1);
|
||||
|
||||
state.write('| ');
|
||||
node.forEach((cell, _, i) => {
|
||||
if (i) state.write(' | ');
|
||||
|
||||
const { length } = state.out;
|
||||
state.render(cell, node, i);
|
||||
cellWidths.push(state.out.length - length);
|
||||
});
|
||||
state.write(' |');
|
||||
|
||||
state.closeBlock(node);
|
||||
|
||||
return cellWidths;
|
||||
};
|
||||
|
||||
const renderHeaderRow = (cellWidths) => {
|
||||
state.flushClose(1);
|
||||
|
||||
state.write('|');
|
||||
node.forEach((cell, _, i) => {
|
||||
if (i) state.write('|');
|
||||
|
||||
state.write(cell.attrs.align === 'center' ? ':' : '-');
|
||||
state.write(state.repeat('-', cellWidths[i]));
|
||||
state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-');
|
||||
});
|
||||
state.write('|');
|
||||
|
||||
state.closeBlock(node);
|
||||
};
|
||||
|
||||
if (isHeaderRow) {
|
||||
renderHeaderRow(renderRow());
|
||||
} else {
|
||||
renderRow();
|
||||
}
|
||||
},
|
||||
[Table.name]: renderTable,
|
||||
[TableCell.name]: renderTableCell,
|
||||
[TableHeader.name]: renderTableCell,
|
||||
[TableRow.name]: renderTableRow,
|
||||
[TaskItem.name]: (state, node) => {
|
||||
state.write(`[${node.attrs.checked ? 'x' : ' '}] `);
|
||||
state.renderContent(node);
|
||||
|
@ -175,7 +132,7 @@ const wrapHtmlPayload = (payload) => `<div>${payload}</div>`;
|
|||
* that parses the Markdown and converts it into HTML.
|
||||
* @returns a markdown serializer
|
||||
*/
|
||||
export default ({ render = () => null, serializerConfig }) => ({
|
||||
export default ({ render = () => null, serializerConfig = {} } = {}) => ({
|
||||
/**
|
||||
* Converts a Markdown string into a ProseMirror JSONDocument based
|
||||
* on a ProseMirror schema.
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
import { uniq } from 'lodash';
|
||||
import { isBlockTablesFeatureEnabled } from './feature_flags';
|
||||
|
||||
const defaultAttrs = {
|
||||
td: { colspan: 1, rowspan: 1, colwidth: null },
|
||||
th: { colspan: 1, rowspan: 1, colwidth: null },
|
||||
};
|
||||
|
||||
const tableMap = new WeakMap();
|
||||
|
||||
function shouldRenderCellInline(cell) {
|
||||
if (cell.childCount === 1) {
|
||||
const parent = cell.child(0);
|
||||
if (parent.type.name === 'paragraph' && parent.childCount === 1) {
|
||||
const child = parent.child(0);
|
||||
return child.isText && child.marks.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getRowsAndCells(table) {
|
||||
const cells = [];
|
||||
const rows = [];
|
||||
table.descendants((n) => {
|
||||
if (n.type.name === 'tableCell' || n.type.name === 'tableHeader') {
|
||||
cells.push(n);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n.type.name === 'tableRow') {
|
||||
rows.push(n);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return { rows, cells };
|
||||
}
|
||||
|
||||
function getChildren(node) {
|
||||
const children = [];
|
||||
for (let i = 0; i < node.childCount; i += 1) {
|
||||
children.push(node.child(i));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function shouldRenderHTMLTable(table) {
|
||||
const { rows, cells } = getRowsAndCells(table);
|
||||
|
||||
const cellChildCount = Math.max(...cells.map((cell) => cell.childCount));
|
||||
const maxColspan = Math.max(...cells.map((cell) => cell.attrs.colspan));
|
||||
const maxRowspan = Math.max(...cells.map((cell) => cell.attrs.rowspan));
|
||||
|
||||
const rowChildren = rows.map((row) => uniq(getChildren(row).map((cell) => cell.type.name)));
|
||||
const cellTypeInFirstRow = rowChildren[0];
|
||||
const cellTypesInOtherRows = uniq(rowChildren.slice(1).map(([type]) => type));
|
||||
|
||||
// if the first row has headers, and there are no headers anywhere else, render markdown table
|
||||
if (
|
||||
!(
|
||||
cellTypeInFirstRow.length === 1 &&
|
||||
cellTypeInFirstRow[0] === 'tableHeader' &&
|
||||
cellTypesInOtherRows.length === 1 &&
|
||||
cellTypesInOtherRows[0] === 'tableCell'
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cellChildCount === 1 && maxColspan === 1 && maxRowspan === 1) {
|
||||
// if all rows contain only one paragraph each and no rowspan/colspan, render markdown table
|
||||
const children = uniq(cells.map((cell) => cell.child(0).type.name));
|
||||
if (children.length === 1 && children[0] === 'paragraph') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function openTag(state, tagName, attrs) {
|
||||
let str = `<${tagName}`;
|
||||
|
||||
str += Object.entries(attrs || {})
|
||||
.map(([key, value]) => {
|
||||
if (defaultAttrs[tagName]?.[key] === value) return '';
|
||||
|
||||
return ` ${key}=${state.quote(value?.toString() || '')}`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return `${str}>`;
|
||||
}
|
||||
|
||||
function closeTag(state, tagName) {
|
||||
return `</${tagName}>`;
|
||||
}
|
||||
|
||||
function isInBlockTable(node) {
|
||||
return tableMap.get(node);
|
||||
}
|
||||
|
||||
function isInTable(node) {
|
||||
return tableMap.has(node);
|
||||
}
|
||||
|
||||
function setIsInBlockTable(table, value) {
|
||||
tableMap.set(table, value);
|
||||
|
||||
const { rows, cells } = getRowsAndCells(table);
|
||||
rows.forEach((row) => tableMap.set(row, value));
|
||||
cells.forEach((cell) => {
|
||||
tableMap.set(cell, value);
|
||||
if (cell.childCount && cell.child(0).type.name === 'paragraph')
|
||||
tableMap.set(cell.child(0), value);
|
||||
});
|
||||
}
|
||||
|
||||
function unsetIsInBlockTable(table) {
|
||||
tableMap.delete(table);
|
||||
|
||||
const { rows, cells } = getRowsAndCells(table);
|
||||
rows.forEach((row) => tableMap.delete(row));
|
||||
cells.forEach((cell) => {
|
||||
tableMap.delete(cell);
|
||||
if (cell.childCount) tableMap.delete(cell.child(0));
|
||||
});
|
||||
}
|
||||
|
||||
function renderTagOpen(state, tagName, attrs) {
|
||||
state.ensureNewLine();
|
||||
state.write(openTag(state, tagName, attrs));
|
||||
}
|
||||
|
||||
function renderTagClose(state, tagName, insertNewline = true) {
|
||||
state.write(closeTag(state, tagName));
|
||||
if (insertNewline) state.ensureNewLine();
|
||||
}
|
||||
|
||||
function renderTableHeaderRowAsMarkdown(state, node, cellWidths) {
|
||||
state.flushClose(1);
|
||||
|
||||
state.write('|');
|
||||
node.forEach((cell, _, i) => {
|
||||
if (i) state.write('|');
|
||||
|
||||
state.write(cell.attrs.align === 'center' ? ':' : '-');
|
||||
state.write(state.repeat('-', cellWidths[i]));
|
||||
state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-');
|
||||
});
|
||||
state.write('|');
|
||||
|
||||
state.closeBlock(node);
|
||||
}
|
||||
|
||||
function renderTableRowAsMarkdown(state, node, isHeaderRow = false) {
|
||||
const cellWidths = [];
|
||||
|
||||
state.flushClose(1);
|
||||
|
||||
state.write('| ');
|
||||
node.forEach((cell, _, i) => {
|
||||
if (i) state.write(' | ');
|
||||
|
||||
const { length } = state.out;
|
||||
state.render(cell, node, i);
|
||||
cellWidths.push(state.out.length - length);
|
||||
});
|
||||
state.write(' |');
|
||||
|
||||
state.closeBlock(node);
|
||||
|
||||
if (isHeaderRow) renderTableHeaderRowAsMarkdown(state, node, cellWidths);
|
||||
}
|
||||
|
||||
function renderTableRowAsHTML(state, node) {
|
||||
renderTagOpen(state, 'tr');
|
||||
|
||||
node.forEach((cell, _, i) => {
|
||||
const tag = cell.type.name === 'tableHeader' ? 'th' : 'td';
|
||||
|
||||
renderTagOpen(state, tag, cell.attrs);
|
||||
|
||||
if (!shouldRenderCellInline(cell)) {
|
||||
state.closeBlock(node);
|
||||
state.flushClose();
|
||||
}
|
||||
|
||||
state.render(cell, node, i);
|
||||
state.flushClose(1);
|
||||
|
||||
renderTagClose(state, tag);
|
||||
});
|
||||
|
||||
renderTagClose(state, 'tr');
|
||||
}
|
||||
|
||||
export function renderTableCell(state, node) {
|
||||
if (!isBlockTablesFeatureEnabled()) {
|
||||
state.renderInline(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInBlockTable(node) || shouldRenderCellInline(node)) {
|
||||
state.renderInline(node.child(0));
|
||||
} else {
|
||||
state.renderContent(node);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderTableRow(state, node) {
|
||||
if (isInBlockTable(node)) {
|
||||
renderTableRowAsHTML(state, node);
|
||||
} else {
|
||||
renderTableRowAsMarkdown(state, node, node.child(0).type.name === 'tableHeader');
|
||||
}
|
||||
}
|
||||
|
||||
export function renderTable(state, node) {
|
||||
if (isBlockTablesFeatureEnabled()) {
|
||||
setIsInBlockTable(node, shouldRenderHTMLTable(node));
|
||||
}
|
||||
|
||||
if (isInBlockTable(node)) renderTagOpen(state, 'table');
|
||||
|
||||
state.renderContent(node);
|
||||
|
||||
if (isInBlockTable(node)) renderTagClose(state, 'table');
|
||||
|
||||
// ensure at least one blank line after any table
|
||||
state.closeBlock(node);
|
||||
state.flushClose();
|
||||
|
||||
if (isBlockTablesFeatureEnabled()) {
|
||||
unsetIsInBlockTable(node);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderHardBreak(state, node, parent, index) {
|
||||
const br = isInTable(parent) ? '<br>' : '\\\n';
|
||||
|
||||
for (let i = index + 1; i < parent.childCount; i += 1) {
|
||||
if (parent.child(i).type !== node.type) {
|
||||
state.write(br);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,7 +133,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<div
|
||||
:class="[$options.userColorScheme, { inline, 'with-codequality': hasCodequalityChanges }]"
|
||||
:class="[
|
||||
$options.userColorScheme,
|
||||
{ 'inline-diff-view': inline, 'with-codequality': hasCodequalityChanges },
|
||||
]"
|
||||
:data-commit-id="commitId"
|
||||
class="diff-grid diff-table code diff-wrap-lines js-syntax-highlight text-file"
|
||||
@mousedown="handleParallelLineMouseDown"
|
||||
|
|
|
@ -613,7 +613,7 @@ table.code {
|
|||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
&.inline-diff-view {
|
||||
.diff-grid-comments {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
|
|
@ -5,5 +5,9 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
|
||||
alias_method :container, :project
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:content_editor_block_tables, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
feature_category :wiki
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: content_editor_block_tables
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66187
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338937
|
||||
milestone: '14.3'
|
||||
type: development
|
||||
group: group::editor
|
||||
default_enabled: false
|
|
@ -188,7 +188,7 @@ module Gitlab
|
|||
merge_requests = MergeRequestsFinder.new(current_user, issuable_params).execute
|
||||
|
||||
unless default_project_filter
|
||||
merge_requests = merge_requests.in_projects(project_ids_relation)
|
||||
merge_requests = merge_requests.of_projects(project_ids_relation)
|
||||
end
|
||||
|
||||
apply_sort(merge_requests, scope: 'merge_requests')
|
||||
|
|
|
@ -0,0 +1,480 @@
|
|||
import Blockquote from '~/content_editor/extensions/blockquote';
|
||||
import Bold from '~/content_editor/extensions/bold';
|
||||
import BulletList from '~/content_editor/extensions/bullet_list';
|
||||
import Code from '~/content_editor/extensions/code';
|
||||
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
|
||||
import Emoji from '~/content_editor/extensions/emoji';
|
||||
import HardBreak from '~/content_editor/extensions/hard_break';
|
||||
import Heading from '~/content_editor/extensions/heading';
|
||||
import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
|
||||
import Image from '~/content_editor/extensions/image';
|
||||
import Italic from '~/content_editor/extensions/italic';
|
||||
import Link from '~/content_editor/extensions/link';
|
||||
import ListItem from '~/content_editor/extensions/list_item';
|
||||
import OrderedList from '~/content_editor/extensions/ordered_list';
|
||||
import Paragraph from '~/content_editor/extensions/paragraph';
|
||||
import Strike from '~/content_editor/extensions/strike';
|
||||
import Table from '~/content_editor/extensions/table';
|
||||
import TableCell from '~/content_editor/extensions/table_cell';
|
||||
import TableHeader from '~/content_editor/extensions/table_header';
|
||||
import TableRow from '~/content_editor/extensions/table_row';
|
||||
import Text from '~/content_editor/extensions/text';
|
||||
import markdownSerializer from '~/content_editor/services/markdown_serializer';
|
||||
import { createTestEditor, createDocBuilder } from '../test_utils';
|
||||
|
||||
jest.mock('~/emoji');
|
||||
|
||||
jest.mock('~/content_editor/services/feature_flags', () => ({
|
||||
isBlockTablesFeatureEnabled: jest.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
const tiptapEditor = createTestEditor({
|
||||
extensions: [
|
||||
Blockquote,
|
||||
Bold,
|
||||
BulletList,
|
||||
Code,
|
||||
CodeBlockHighlight,
|
||||
Emoji,
|
||||
HardBreak,
|
||||
Heading,
|
||||
HorizontalRule,
|
||||
Image,
|
||||
Italic,
|
||||
Link,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
Paragraph,
|
||||
Strike,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Text,
|
||||
],
|
||||
});
|
||||
|
||||
const {
|
||||
builders: {
|
||||
doc,
|
||||
blockquote,
|
||||
bold,
|
||||
bulletList,
|
||||
code,
|
||||
codeBlock,
|
||||
emoji,
|
||||
heading,
|
||||
hardBreak,
|
||||
horizontalRule,
|
||||
image,
|
||||
italic,
|
||||
link,
|
||||
listItem,
|
||||
orderedList,
|
||||
paragraph,
|
||||
strike,
|
||||
table,
|
||||
tableCell,
|
||||
tableHeader,
|
||||
tableRow,
|
||||
},
|
||||
} = createDocBuilder({
|
||||
tiptapEditor,
|
||||
names: {
|
||||
blockquote: { nodeType: Blockquote.name },
|
||||
bold: { markType: Bold.name },
|
||||
bulletList: { nodeType: BulletList.name },
|
||||
code: { markType: Code.name },
|
||||
codeBlock: { nodeType: CodeBlockHighlight.name },
|
||||
emoji: { markType: Emoji.name },
|
||||
hardBreak: { nodeType: HardBreak.name },
|
||||
heading: { nodeType: Heading.name },
|
||||
horizontalRule: { nodeType: HorizontalRule.name },
|
||||
image: { nodeType: Image.name },
|
||||
italic: { nodeType: Italic.name },
|
||||
link: { markType: Link.name },
|
||||
listItem: { nodeType: ListItem.name },
|
||||
orderedList: { nodeType: OrderedList.name },
|
||||
paragraph: { nodeType: Paragraph.name },
|
||||
strike: { markType: Strike.name },
|
||||
table: { nodeType: Table.name },
|
||||
tableCell: { nodeType: TableCell.name },
|
||||
tableHeader: { nodeType: TableHeader.name },
|
||||
tableRow: { nodeType: TableRow.name },
|
||||
},
|
||||
});
|
||||
|
||||
const serialize = (...content) =>
|
||||
markdownSerializer({}).serialize({
|
||||
schema: tiptapEditor.schema,
|
||||
content: doc(...content).toJSON(),
|
||||
});
|
||||
|
||||
describe('markdownSerializer', () => {
|
||||
it('correctly serializes a line break', () => {
|
||||
expect(serialize(paragraph('hello', hardBreak(), 'world'))).toBe('hello\\\nworld');
|
||||
});
|
||||
|
||||
it('correctly serializes a table with inline content', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
// each table cell must contain at least one paragraph
|
||||
tableRow(
|
||||
tableHeader(paragraph('header')),
|
||||
tableHeader(paragraph('header')),
|
||||
tableHeader(paragraph('header')),
|
||||
),
|
||||
tableRow(
|
||||
tableCell(paragraph('cell')),
|
||||
tableCell(paragraph('cell')),
|
||||
tableCell(paragraph('cell')),
|
||||
),
|
||||
tableRow(
|
||||
tableCell(paragraph('cell')),
|
||||
tableCell(paragraph('cell')),
|
||||
tableCell(paragraph('cell')),
|
||||
),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
| header | header | header |
|
||||
|--------|--------|--------|
|
||||
| cell | cell | cell |
|
||||
| cell | cell | cell |
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes a table with line breaks', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('header')), tableHeader(paragraph('header'))),
|
||||
tableRow(
|
||||
tableCell(paragraph('cell with', hardBreak(), 'line', hardBreak(), 'breaks')),
|
||||
tableCell(paragraph('cell')),
|
||||
),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
| header | header |
|
||||
|--------|--------|
|
||||
| cell with<br>line<br>breaks | cell |
|
||||
| cell | cell |
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes two consecutive tables', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('header')), tableHeader(paragraph('header'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
),
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('header')), tableHeader(paragraph('header'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
| header | header |
|
||||
|--------|--------|
|
||||
| cell | cell |
|
||||
| cell | cell |
|
||||
|
||||
| header | header |
|
||||
|--------|--------|
|
||||
| cell | cell |
|
||||
| cell | cell |
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes a table with block content', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(
|
||||
tableHeader(paragraph('examples of')),
|
||||
tableHeader(paragraph('block content')),
|
||||
tableHeader(paragraph('in tables')),
|
||||
tableHeader(paragraph('in content editor')),
|
||||
),
|
||||
tableRow(
|
||||
tableCell(heading({ level: 1 }, 'heading 1')),
|
||||
tableCell(heading({ level: 2 }, 'heading 2')),
|
||||
tableCell(paragraph(bold('just bold'))),
|
||||
tableCell(paragraph(bold('bold'), ' ', italic('italic'), ' ', code('code'))),
|
||||
),
|
||||
tableRow(
|
||||
tableCell(
|
||||
paragraph('all marks in three paragraphs:'),
|
||||
paragraph('the ', bold('quick'), ' ', italic('brown'), ' ', code('fox')),
|
||||
paragraph(
|
||||
link({ href: '/home' }, 'jumps'),
|
||||
' over the ',
|
||||
strike('lazy'),
|
||||
' ',
|
||||
emoji({ name: 'dog' }),
|
||||
),
|
||||
),
|
||||
tableCell(
|
||||
paragraph(image({ src: 'img.jpg', alt: 'some image' }), hardBreak(), 'image content'),
|
||||
),
|
||||
tableCell(
|
||||
blockquote('some text', hardBreak(), hardBreak(), 'in a multiline blockquote'),
|
||||
),
|
||||
tableCell(
|
||||
codeBlock(
|
||||
{ language: 'javascript' },
|
||||
'var a = 2;\nvar b = 3;\nvar c = a + d;\n\nconsole.log(c);',
|
||||
),
|
||||
),
|
||||
),
|
||||
tableRow(
|
||||
tableCell(bulletList(listItem('item 1'), listItem('item 2'), listItem('item 2'))),
|
||||
tableCell(orderedList(listItem('item 1'), listItem('item 2'), listItem('item 2'))),
|
||||
tableCell(
|
||||
paragraph('paragraphs separated by'),
|
||||
horizontalRule(),
|
||||
paragraph('a horizontal rule'),
|
||||
),
|
||||
tableCell(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('table')), tableHeader(paragraph('inside'))),
|
||||
tableRow(tableCell(paragraph('another')), tableCell(paragraph('table'))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
<table>
|
||||
<tr>
|
||||
<th>examples of</th>
|
||||
<th>block content</th>
|
||||
<th>in tables</th>
|
||||
<th>in content editor</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
# heading 1
|
||||
</td>
|
||||
<td>
|
||||
|
||||
## heading 2
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**just bold**
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**bold** _italic_ \`code\`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
all marks in three paragraphs:
|
||||
|
||||
the **quick** _brown_ \`fox\`
|
||||
|
||||
[jumps](/home) over the ~~lazy~~ :dog:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
![some image](img.jpg)<br>image content
|
||||
</td>
|
||||
<td>
|
||||
|
||||
> some text\\
|
||||
> \\
|
||||
> in a multiline blockquote
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\`\`\`javascript
|
||||
var a = 2;
|
||||
var b = 3;
|
||||
var c = a + d;
|
||||
|
||||
console.log(c);
|
||||
\`\`\`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2
|
||||
</td>
|
||||
<td>
|
||||
|
||||
1. item 1
|
||||
2. item 2
|
||||
3. item 2
|
||||
</td>
|
||||
<td>
|
||||
|
||||
paragraphs separated by
|
||||
|
||||
---
|
||||
|
||||
a horizontal rule
|
||||
</td>
|
||||
<td>
|
||||
|
||||
| table | inside |
|
||||
|-------|--------|
|
||||
| another | table |
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly renders content after a markdown table', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(tableRow(tableHeader(paragraph('header'))), tableRow(tableCell(paragraph('cell')))),
|
||||
heading({ level: 1 }, 'this is a heading'),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
| header |
|
||||
|--------|
|
||||
| cell |
|
||||
|
||||
# this is a heading
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly renders content after an html table', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('header'))),
|
||||
tableRow(tableCell(blockquote('hi'), paragraph('there'))),
|
||||
),
|
||||
heading({ level: 1 }, 'this is a heading'),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
<table>
|
||||
<tr>
|
||||
<th>header</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
> hi
|
||||
|
||||
there
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
# this is a heading
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes tables with misplaced header cells', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableHeader(paragraph('cell'))),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
<table>
|
||||
<tr>
|
||||
<th>cell</th>
|
||||
<td>cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>cell</td>
|
||||
<th>cell</th>
|
||||
</tr>
|
||||
</table>
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes table without any headers', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
tableRow(tableCell(paragraph('cell')), tableCell(paragraph('cell'))),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
<table>
|
||||
<tr>
|
||||
<td>cell</td>
|
||||
<td>cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>cell</td>
|
||||
<td>cell</td>
|
||||
</tr>
|
||||
</table>
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly serializes table with rowspan and colspan', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(
|
||||
tableHeader(paragraph('header')),
|
||||
tableHeader(paragraph('header')),
|
||||
tableHeader(paragraph('header')),
|
||||
),
|
||||
tableRow(
|
||||
tableCell({ colspan: 2 }, paragraph('cell with rowspan: 2')),
|
||||
tableCell({ rowspan: 2 }, paragraph('cell')),
|
||||
),
|
||||
tableRow(tableCell({ colspan: 2 }, paragraph('cell with rowspan: 2'))),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
<table>
|
||||
<tr>
|
||||
<th>header</th>
|
||||
<th>header</th>
|
||||
<th>header</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">cell with rowspan: 2</td>
|
||||
<td rowspan="2">cell</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">cell with rowspan: 2</td>
|
||||
</tr>
|
||||
</table>
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -102,14 +102,10 @@
|
|||
markdown: |-
|
||||
| header | header |
|
||||
|--------|--------|
|
||||
| cell | cell |
|
||||
| cell | cell |
|
||||
- name: table_with_alignment
|
||||
markdown: |-
|
||||
| header | : header : | header : |
|
||||
|--------|------------|----------|
|
||||
| cell | cell | cell |
|
||||
| cell | cell | cell |
|
||||
| `code` | cell with **bold** |
|
||||
| ~~strike~~ | cell with _italic_ |
|
||||
|
||||
# content after table
|
||||
- name: emoji
|
||||
markdown: ':sparkles: :heart: :100:'
|
||||
- name: reference
|
||||
|
|
|
@ -148,13 +148,13 @@ RSpec.describe Gitlab::SearchResults do
|
|||
end
|
||||
end
|
||||
|
||||
it 'includes merge requests from source and target projects' do
|
||||
it 'does not include merge requests from source projects' do
|
||||
forked_project = fork_project(project, user)
|
||||
merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo')
|
||||
|
||||
results = described_class.new(user, 'foo', Project.where(id: forked_project.id))
|
||||
|
||||
expect(results.objects('merge_requests')).to include merge_request_2
|
||||
expect(results.objects('merge_requests')).not_to include merge_request_2
|
||||
end
|
||||
|
||||
describe '#merge_requests' do
|
||||
|
|
Loading…
Reference in New Issue