From 85ded525c8fbeffffeb4158dd7ed5502bb762a9c Mon Sep 17 00:00:00 2001
From: Enrique Alcantara <ealcantara@gitlab.com>
Date: Wed, 31 Aug 2022 14:11:00 -0400
Subject: [PATCH] Support client-side deserialization of table of contents

Allow the content editor to deserialize table of contents
using the client-side GitLab flavored markdown parser
---
 .../content_editor/extensions/sourcemap.js           |  2 ++
 .../content_editor/services/markdown_serializer.js   |  4 ++--
 .../services/remark_markdown_deserializer.js         |  6 ++++++
 .../remark_markdown_processing_spec.js               | 12 ++++++++++++
 .../render_html_and_json_for_all_examples.js         |  2 ++
 5 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
index d8626289f2c69..54d69d83188db 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 5fc7204212b11..ba0cad6c91c42 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 9113ad5997e96..ca290efca11df 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 61a6fd0debe71..bc43af9bd8b51 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 fc88c0ea44540..bd48b7fdd2323 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,
-- 
GitLab