diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index d8626289f2c6935c1db566e7a6748fc7d0a7f5db..54d69d83188dbb3ff68471344766cd636ab2d8a6 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -27,6 +27,7 @@ import Table from './table'; import TableCell from './table_cell'; import TableHeader from './table_header'; import TableRow from './table_row'; +import TableOfContents from './table_of_contents'; import Video from './video'; export default Extension.create({ @@ -61,6 +62,7 @@ export default Extension.create({ TableCell.name, TableHeader.name, TableRow.name, + TableOfContents.name, Video.name, ...HTMLNodes.map((htmlNode) => htmlNode.name), ], diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 5fc7204212b116a977a61400d3207bb5ef71488e..ba0cad6c91c426dbc403d39734773a20e9d1d7a3 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -206,10 +206,10 @@ const defaultSerializerConfig = { }, overwriteSourcePreservationStrategy: true, }), - [TableOfContents.name]: (state, node) => { + [TableOfContents.name]: preserveUnchanged((state, node) => { state.write('[[_TOC_]]'); state.closeBlock(node); - }, + }), [Table.name]: preserveUnchanged(renderTable), [TableCell.name]: renderTableCell, [TableHeader.name]: renderTableCell, 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 9113ad5997e9611528dd380f8312bac59fac05d4..ca290efca11df56b87e52e0e39c0442890abcfa6 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -237,6 +237,11 @@ const factorySpecs = { language: hastNode.properties.language, }), }, + + tableOfContents: { + type: 'block', + selector: 'tableofcontents', + }, }; const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url', 'isReference']; @@ -294,6 +299,7 @@ export default () => { 'yaml', 'toml', 'json', + 'tableOfContents', ], }); diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js index 61a6fd0debe7107a0116fa35da37b5871cd3f251..bc43af9bd8b515ca3fee980fb61bc135680ce8dc 100644 --- a/spec/frontend/content_editor/remark_markdown_processing_spec.js +++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js @@ -23,6 +23,7 @@ import Sourcemap from '~/content_editor/extensions/sourcemap'; import Strike from '~/content_editor/extensions/strike'; import Table from '~/content_editor/extensions/table'; import TableHeader from '~/content_editor/extensions/table_header'; +import TableOfContents from '~/content_editor/extensions/table_of_contents'; import TableRow from '~/content_editor/extensions/table_row'; import TableCell from '~/content_editor/extensions/table_cell'; import TaskList from '~/content_editor/extensions/task_list'; @@ -61,6 +62,7 @@ const tiptapEditor = createTestEditor({ TableRow, TableHeader, TableCell, + TableOfContents, TaskList, TaskItem, Video, @@ -98,6 +100,7 @@ const { tableRow, tableHeader, tableCell, + tableOfContents, taskItem, taskList, video, @@ -130,6 +133,7 @@ const { tableCell: { nodeType: TableCell.name }, tableHeader: { nodeType: TableHeader.name }, tableRow: { nodeType: TableRow.name }, + tableOfContents: { nodeType: TableOfContents.name }, taskItem: { nodeType: TaskItem.name }, taskList: { nodeType: TaskList.name }, video: { nodeType: Video.name }, @@ -1294,6 +1298,14 @@ content expectedDoc: doc(diagram({ ...source(markdown), language }, 'content')), }; }), + { + markdown: '[[_TOC_]]', + expectedDoc: doc(tableOfContents(source('[[_TOC_]]'))), + }, + { + markdown: '[TOC]', + expectedDoc: doc(tableOfContents(source('[TOC]'))), + }, ]; const runOnly = examples.find((example) => example.only === true); diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js index fc88c0ea44540af535d7ca768e8b051ae4693e6e..bd48b7fdd23236a66884f9cdf5d5ca11aa556ff3 100644 --- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js +++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js @@ -34,6 +34,7 @@ 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 TableOfContents from '~/content_editor/extensions/table_of_contents'; import TaskItem from '~/content_editor/extensions/task_item'; import TaskList from '~/content_editor/extensions/task_list'; import Video from '~/content_editor/extensions/video'; @@ -75,6 +76,7 @@ const tiptapEditor = createTestEditor({ TableCell, TableHeader, TableRow, + TableOfContents, TaskItem, TaskList, Video,