Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-09-06 12:12:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-09-06 12:12:58 +0300
commit6431ee6152dbcae3288690bc8c81e5feae2e737c (patch)
treebf71b9773b1e6527ced0380409c965d308a4b747 /app/assets/javascripts/lib
parent5947e68ac310a25614438df36149b27b0654eb5c (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/gfm/constants.js10
-rw-r--r--app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js85
-rw-r--r--app/assets/javascripts/lib/gfm/index.js11
-rw-r--r--app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js1
4 files changed, 104 insertions, 3 deletions
diff --git a/app/assets/javascripts/lib/gfm/constants.js b/app/assets/javascripts/lib/gfm/constants.js
new file mode 100644
index 00000000000..eaabeb2a767
--- /dev/null
+++ b/app/assets/javascripts/lib/gfm/constants.js
@@ -0,0 +1,10 @@
+export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN = '[[';
+export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN = 'TOC';
+export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN = ']]';
+export const TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN = '[TOC]';
+
+export const MDAST_TEXT_NODE = 'text';
+export const MDAST_EMPHASIS_NODE = 'emphasis';
+export const MDAST_PARAGRAPH_NODE = 'paragraph';
+
+export const GLFM_TABLE_OF_CONTENTS_NODE = 'tableOfContents';
diff --git a/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js b/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js
new file mode 100644
index 00000000000..4d2484a657a
--- /dev/null
+++ b/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js
@@ -0,0 +1,85 @@
+import { first, last } from 'lodash';
+import { u } from 'unist-builder';
+import { visitParents, SKIP, CONTINUE } from 'unist-util-visit-parents';
+import {
+ TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN,
+ TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN,
+ TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN,
+ TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN,
+ MDAST_TEXT_NODE,
+ MDAST_EMPHASIS_NODE,
+ MDAST_PARAGRAPH_NODE,
+ GLFM_TABLE_OF_CONTENTS_NODE,
+} from '../constants';
+
+const isTOCTextNode = ({ type, value }) =>
+ type === MDAST_TEXT_NODE && value === TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN;
+
+const isTOCEmphasisNode = ({ type, children }) =>
+ type === MDAST_EMPHASIS_NODE && children.length === 1 && isTOCTextNode(first(children));
+
+const isTOCDoubleSquareBracketOpenTokenTextNode = ({ type, value }) =>
+ type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN;
+
+const isTOCDoubleSquareBracketCloseTokenTextNode = ({ type, value }) =>
+ type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN;
+
+/*
+ * Detects table of contents declaration with syntax [[_TOC_]]
+ */
+const isTableOfContentsDoubleSquareBracketSyntax = ({ children }) => {
+ if (children.length !== 3) {
+ return false;
+ }
+
+ const [firstChild, middleChild, lastChild] = children;
+
+ return (
+ isTOCDoubleSquareBracketOpenTokenTextNode(firstChild) &&
+ isTOCEmphasisNode(middleChild) &&
+ isTOCDoubleSquareBracketCloseTokenTextNode(lastChild)
+ );
+};
+
+/*
+ * Detects table of contents declaration with syntax [TOC]
+ */
+const isTableOfContentsSingleSquareBracketSyntax = ({ children }) => {
+ if (children.length !== 1) {
+ return false;
+ }
+
+ const [firstChild] = children;
+ const { type, value } = firstChild;
+
+ return type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN;
+};
+
+const isTableOfContentsNode = (node) =>
+ node.type === MDAST_PARAGRAPH_NODE &&
+ (isTableOfContentsDoubleSquareBracketSyntax(node) ||
+ isTableOfContentsSingleSquareBracketSyntax(node));
+
+export default () => {
+ return (tree) => {
+ visitParents(tree, (node, ancestors) => {
+ const parent = last(ancestors);
+
+ if (!parent) {
+ return CONTINUE;
+ }
+
+ if (isTableOfContentsNode(node)) {
+ const index = parent.children.indexOf(node);
+
+ parent.children[index] = u(GLFM_TABLE_OF_CONTENTS_NODE, {
+ position: node.position,
+ });
+ }
+
+ return SKIP;
+ });
+
+ return tree;
+ };
+};
diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js
index eaf653e9924..fad73f93c1a 100644
--- a/app/assets/javascripts/lib/gfm/index.js
+++ b/app/assets/javascripts/lib/gfm/index.js
@@ -6,6 +6,8 @@ import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';
import remarkRehype, { all } from 'remark-rehype';
import rehypeRaw from 'rehype-raw';
+import glfmTableOfContents from './glfm_extensions/table_of_contents';
+import * as glfmMdastToHastHandlers from './mdast_to_hast_handlers/glfm_mdast_to_hast_handlers';
const skipFrontmatterHandler = (language) => (h, node) =>
h(node.position, 'frontmatter', { language }, [{ type: 'text', value: node.value }]);
@@ -65,19 +67,22 @@ const skipRenderingHandlers = {
all(h, node),
);
},
+ tableOfContents: (h, node) => h(node.position, 'tableOfContents'),
toml: skipFrontmatterHandler('toml'),
yaml: skipFrontmatterHandler('yaml'),
json: skipFrontmatterHandler('json'),
};
-const createParser = ({ skipRendering = [] }) => {
+const createParser = ({ skipRendering }) => {
return unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkFrontmatter, ['yaml', 'toml', { type: 'json', marker: ';' }])
+ .use(glfmTableOfContents)
.use(remarkRehype, {
allowDangerousHtml: true,
handlers: {
+ ...glfmMdastToHastHandlers,
...pick(skipRenderingHandlers, skipRendering),
},
})
@@ -99,13 +104,13 @@ const compilerFactory = (renderer) =>
* tree in any desired representation
*
* @param {String} params.markdown Markdown to parse
- * @param {(tree: MDast -> any)} params.renderer A function that accepts mdast
+ * @param {Function} params.renderer A function that accepts mdast
* AST tree and returns an object of any type that represents the result of
* rendering the tree. See the references below to for more information
* about MDast.
*
* MDastTree documentation https://github.com/syntax-tree/mdast
- * @returns {Promise<any>} Returns a promise with the result of rendering
+ * @returns {Promise} Returns a promise with the result of rendering
* the MDast tree
*/
export const render = async ({ markdown, renderer, skipRendering = [] }) => {
diff --git a/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js b/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js
new file mode 100644
index 00000000000..91b09e69405
--- /dev/null
+++ b/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js
@@ -0,0 +1 @@
+export const tableOfContents = (h, node) => h(node.position, 'nav');