Commit 8a03dbf8 authored by Douwe Maan's avatar Douwe Maan

Use nodes and marks to power Copy-as-GFM

The spec needed to be updated because in some cases the resulting
Markdown is slightly different, though equally valid.
parent a7e77e10
Pipeline #44618912 passed with stages
in 51 minutes and 5 seconds
import Doc from './nodes/doc';
import Paragraph from './nodes/paragraph';
import Text from './nodes/text';
import Blockquote from './nodes/blockquote';
import CodeBlock from './nodes/code_block';
import HardBreak from './nodes/hard_break';
import Heading from './nodes/heading';
import HorizontalRule from './nodes/horizontal_rule';
import Image from './nodes/image';
import Table from './nodes/table';
import TableHead from './nodes/table_head';
import TableBody from './nodes/table_body';
import TableHeaderRow from './nodes/table_header_row';
import TableRow from './nodes/table_row';
import TableCell from './nodes/table_cell';
import Emoji from './nodes/emoji';
import Reference from './nodes/reference';
import TableOfContents from './nodes/table_of_contents';
import Video from './nodes/video';
import BulletList from './nodes/bullet_list';
import OrderedList from './nodes/ordered_list';
import ListItem from './nodes/list_item';
import DescriptionList from './nodes/description_list';
import DescriptionTerm from './nodes/description_term';
import DescriptionDetails from './nodes/description_details';
import TaskList from './nodes/task_list';
import OrderedTaskList from './nodes/ordered_task_list';
import TaskListItem from './nodes/task_list_item';
import Summary from './nodes/summary';
import Details from './nodes/details';
import Bold from './marks/bold';
import Italic from './marks/italic';
import Strike from './marks/strike';
import InlineDiff from './marks/inline_diff';
import Link from './marks/link';
import Code from './marks/code';
import MathMark from './marks/math';
import InlineHTML from './marks/inline_html';
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb transform
// GitLab Flavored Markdown (GFM) to HTML.
// The nodes and marks referenced here transform that same HTML to GFM to be copied to the clipboard.
// Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
// from GFM should have a node or mark here.
// The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
export default [
new Doc(),
new Paragraph(),
new Text(),
new Blockquote(),
new CodeBlock(),
new HardBreak(),
new Heading({ maxLevel: 6 }),
new HorizontalRule(),
new Image(),
new Table(),
new TableHead(),
new TableBody(),
new TableHeaderRow(),
new TableRow(),
new TableCell(),
new Emoji(),
new Reference(),
new TableOfContents(),
new Video(),
new BulletList(),
new OrderedList(),
new ListItem(),
new DescriptionList(),
new DescriptionTerm(),
new DescriptionDetails(),
new TaskList(),
new OrderedTaskList(),
new TaskListItem(),
new Summary(),
new Details(),
new Bold(),
new Italic(),
new Strike(),
new InlineDiff(),
new Link(),
new Code(),
new MathMark(),
new InlineHTML(),
];
import { Schema } from 'prosemirror-model';
import editorExtensions from './editor_extensions';
const nodes = editorExtensions
.filter(extension => extension.type === 'node')
.reduce(
(ns, { name, schema }) => ({
...ns,
[name]: schema,
}),
{},
);
const marks = editorExtensions
.filter(extension => extension.type === 'mark')
.reduce(
(ms, { name, schema }) => ({
...ms,
[name]: schema,
}),
{},
);
export default new Schema({ nodes, marks });
import { MarkdownSerializer } from 'prosemirror-markdown';
import editorExtensions from './editor_extensions';
const nodes = editorExtensions
.filter(extension => extension.type === 'node')
.reduce(
(ns, { name, toMarkdown }) => ({
...ns,
[name]: toMarkdown,
}),
{},
);
const marks = editorExtensions
.filter(extension => extension.type === 'mark')
.reduce(
(ms, { name, toMarkdown }) => ({
...ms,
[name]: toMarkdown,
}),
{},
);
export default new MarkdownSerializer(nodes, marks);
import $ from 'jquery';
import Mousetrap from 'mousetrap';
import _ from 'underscore';
import Sidebar from '../../right_sidebar';
import Shortcuts from './shortcuts';
import { CopyAsGFM } from '../markdown/copy_as_gfm';
......@@ -63,18 +62,18 @@ export default class ShortcutsIssuable extends Shortcuts {
}
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const selected = CopyAsGFM.nodeToGFM(el);
const blockquoteEl = document.createElement('blockquote');
blockquoteEl.appendChild(el);
const text = CopyAsGFM.nodeToGFM(blockquoteEl);
if (selected.trim() === '') {
if (text.trim() === '') {
return false;
}
const quote = _.map(selected.split('\n'), val => `${`> ${val}`.trim()}\n`);
// If replyField already has some content, add a newline before our quote
const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
$replyField
.val((a, current) => `${current}${separator}${quote.join('')}\n`)
.val((a, current) => `${current}${separator}${text}\n\n`)
.trigger('input')
.trigger('change');
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/emoji.js
module Banzai
module Filter
# HTML filter that replaces :emoji: and unicode with images.
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/image.js
module Banzai
module Filter
# HTML filter that moves the value of image `src` attributes to `data-src`
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/image.js
module Banzai
module Filter
# HTML filter that wraps links around inline images.
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/marks/inline_diff.js
module Banzai
module Filter
class InlineDiffFilter < HTML::Pipeline::Filter
......
......@@ -2,6 +2,9 @@
require 'uri'
# Generated HTML is transformed back to GFM by:
# - app/assets/javascripts/behaviors/markdown/marks/math.js
# - app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
# HTML filter that adds class="code math" and removes the dollar sign in $`2+2`$.
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
class MermaidFilter < HTML::Pipeline::Filter
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/reference.js
module Banzai
module Filter
# Base class for GitLab Flavored Markdown reference filters.
......
......@@ -3,6 +3,7 @@
require 'rouge/plugins/common_mark'
require 'rouge/plugins/redcarpet'
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
# HTML Filter to highlight fenced code blocks
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
module Banzai
module Filter
# HTML filter that adds an anchor child element to all Headers in a
......
......@@ -2,6 +2,10 @@
require 'task_list/filter'
# Generated HTML is transformed back to GFM by:
# - app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
# - app/assets/javascripts/behaviors/markdown/nodes/task_list.js
# - app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
module Banzai
module Filter
class TaskListFilter < TaskList::Filter
......
# frozen_string_literal: true
# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/video.js
module Banzai
module Filter
# Find every image that isn't already wrapped in an `a` tag, and that has
......
......@@ -3,11 +3,11 @@
module Banzai
module Pipeline
class GfmPipeline < BasePipeline
# These filters convert GitLab Flavored Markdown (GFM) to HTML.
# The handlers defined in app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
# consequently convert that same HTML to GFM to be copied to the clipboard.
# Every filter that generates HTML from GFM should have a handler in
# app/assets/javascripts/behaviors/markdown/copy_as_gfm.js, in reverse order.
# These filters transform GitLab Flavored Markdown (GFM) to HTML.
# The nodes and marks referenced in app/assets/javascripts/behaviors/markdown/editor_extensions.js
# consequently transform that same HTML to GFM to be copied to the clipboard.
# Every filter that generates HTML from GFM should have a node or mark in
# app/assets/javascripts/behaviors/markdown/editor_extensions.js.
# The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
def self.filters
@filters ||= FilterArray[
......
This diff is collapsed.
......@@ -87,7 +87,7 @@ describe('CopyAsGFM', () => {
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
const expectedGFM = '- List Item1\n- List Item2';
const expectedGFM = '* List Item1\n\n* List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
......@@ -97,7 +97,7 @@ describe('CopyAsGFM', () => {
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
const expectedGFM = '1. List Item1\n1. List Item2';
const expectedGFM = '1. List Item1\n\n1. List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment