浏览代码

Update generator.js

egon-r 1 年之前
父节点
当前提交
22ef6206ce
共有 1 个文件被更改,包括 124 次插入51 次删除
  1. 124 51
      src/generator.js

+ 124 - 51
src/generator.js

@@ -1,6 +1,6 @@
 /**
- * @typedef {import('hast').Element} Element
- * @typedef {import('hast').Root} Root
+ * @typedef {import("hast").Element} Element
+ * @typedef {import("hast").Root} Root
  * @typedef Options options
  *   Configuration.
  * @property {boolean} [showLineNumbers]
@@ -12,16 +12,16 @@
  *   Note: The language must be registered with refractor.
  */
 
-import { visit } from 'unist-util-visit'
-import { toString } from 'hast-util-to-string'
-import { filter } from 'unist-util-filter'
-import rangeParser from 'parse-numeric-range'
+import { visit } from "unist-util-visit"
+import { toString } from "hast-util-to-string"
+import { filter } from "unist-util-filter"
+import rangeParser from "parse-numeric-range"
 
 const getLanguage = (node) => {
   const className = node.properties.className
   //@ts-ignore
   for (const classListItem of className) {
-    if (classListItem.slice(0, 9) === 'language-') {
+    if (classListItem.slice(0, 9) === "language-") {
       return classListItem.slice(9).toLowerCase()
     }
   }
@@ -29,7 +29,7 @@ const getLanguage = (node) => {
 }
 
 /**
- * @param {import('refractor/lib/core').Refractor} refractor
+ * @param {import("refractor/lib/core").Refractor} refractor
  * @param {string} defaultLanguage
  * @return {void}
  */
@@ -49,7 +49,7 @@ const calculateLinesToHighlight = (meta) => {
   const RE = /{([\d,-]+)}/
   // Remove space between {} e.g. {1, 3}
   const parsedMeta = meta
-    .split(',')
+    .split(",")
     .map((str) => str.trim())
     .join()
   if (RE.test(parsedMeta)) {
@@ -89,8 +89,8 @@ const createLineNodes = (number) => {
   const a = new Array(number)
   for (let i = 0; i < number; i++) {
     a[i] = {
-      type: 'element',
-      tagName: 'span',
+      type: "element",
+      tagName: "span",
       properties: { className: [] },
       children: [],
     }
@@ -102,20 +102,20 @@ const createLineNodes = (number) => {
  * Split multiline text nodes into individual nodes with positioning
  * Add a node start and end line position information for each text node
  *
- * @return { (ast:Element['children']) => Element['children'] }
+ * @return { (ast:Element["children"]) => Element["children"] }
  *
  */
 const addNodePositionClosure = () => {
   let startLineNum = 1
   /**
-   * @param {Element['children']} ast
-   * @return {Element['children']}
+   * @param {Element["children"]} ast
+   * @return {Element["children"]}
    */
   const addNodePosition = (ast) => {
     return ast.reduce((result, node) => {
-      if (node.type === 'text') {
+      if (node.type === "text") {
         const value = /** @type {string} */ (node.value)
-        const numLines = (value.match(/\n/g) || '').length
+        const numLines = (value.match(/\n/g) || "").length
         if (numLines === 0) {
           node.position = {
             // column: 1 is needed to avoid error with @next/mdx
@@ -125,11 +125,11 @@ const addNodePositionClosure = () => {
           }
           result.push(node)
         } else {
-          const lines = value.split('\n')
+          const lines = value.split("\n")
           for (const [i, line] of lines.entries()) {
             result.push({
-              type: 'text',
-              value: i === lines.length - 1 ? line : line + '\n',
+              type: "text",
+              value: i === lines.length - 1 ? line : line + "\n",
               position: {
                 start: { line: startLineNum + i, column: 1 },
                 end: { line: startLineNum + i, column: 1 },
@@ -142,7 +142,7 @@ const addNodePositionClosure = () => {
         return result
       }
 
-      if (Object.prototype.hasOwnProperty.call(node, 'children')) {
+      if (Object.prototype.hasOwnProperty.call(node, "children")) {
         const initialLineNum = startLineNum
         // @ts-ignore
         node.children = addNodePosition(node.children, startLineNum)
@@ -161,20 +161,95 @@ const addNodePositionClosure = () => {
   return addNodePosition
 }
 
+/**
+ * @param {Element} parent 
+ * @param {string} meta 
+ */
+const createToolbarElement = (parent, meta) => {
+  const toolbarShowRx = /toolbar-.*/
+  if (!toolbarShowRx.test(meta)) {
+    return
+  }
+
+  const toolbarTitleRx = /toolbar-title="(.*)"/
+  const toolbarTitle = toolbarTitleRx.exec(meta)[1]
+
+  parent.children.push({
+    type: "element",
+    tagName: "div",
+    properties: { className: ["toolbar-container"] },
+    children: [
+      {
+        type: "element",
+        tagName: "div",
+        properties: { className: ["toolbar"] },
+        children: [
+          {
+            type: "element",
+            tagName: "div",
+            properties: { className: ["toolbar-title"] },
+            children: [
+              {
+                type: "text",
+                value: toolbarTitle,
+              }
+            ]
+          },
+          {
+            type: "element",
+            tagName: "div",
+            properties: {
+              className: ["toolbar-buttons"],
+            },
+            children: [
+              {
+                type: "element",
+                tagName: "button",
+                properties: {
+                  className: ["toolbar-download-code-button"],
+                },
+                children: [
+                  {
+                    type: "text",
+                    value: "DOWNLOAD"
+                  },
+                ]
+              },
+              {
+                type: "element",
+                tagName: "button",
+                properties: {
+                  className: ["toolbar-copy-code-button"],
+                },
+                children: [
+                  {
+                    type: "text",
+                    value: "COPY"
+                  },
+                ]
+              }
+            ]
+          }
+        ],
+      }
+    ],
+  })
+}
+
 /**
  * Rehype prism plugin generator that highlights code blocks with refractor (prismjs)
  *
  * Pass in your own refractor object with the required languages registered:
  * https://github.com/wooorm/refractor#refractorregistersyntax
  *
- * @param {import('refractor/lib/core').Refractor} refractor
- * @return {import('unified').Plugin<[Options?], Root>}
+ * @param {import("refractor/lib/core").Refractor} refractor
+ * @return {import("unified").Plugin<[Options?], Root>}
  */
 const rehypePrismGenerator = (refractor) => {
   return (options = {}) => {
     checkIfLanguageIsRegistered(refractor, options.defaultLanguage)
     return (tree) => {
-      visit(tree, 'element', visitor)
+      visit(tree, "element", visitor)
     }
 
     /**
@@ -183,15 +258,15 @@ const rehypePrismGenerator = (refractor) => {
      * @param {Element} parent
      */
     function visitor(node, index, parent) {
-      if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
+      if (!parent || parent.tagName !== "pre" || node.tagName !== "code") {
         return
       }
 
       // @ts-ignore meta is a custom code block property
-      let meta = /** @type {string} */ (node?.data?.meta || node?.properties?.metastring || '')
+      let meta = /** @type {string} */ (node?.data?.meta || node?.properties?.metastring || "")
       // Coerce className to array
       if (node.properties.className) {
-        if (typeof node.properties.className === 'boolean') {
+        if (typeof node.properties.className === "boolean") {
           node.properties.className = []
         } else if (!Array.isArray(node.properties.className)) {
           node.properties.className = [node.properties.className]
@@ -201,12 +276,11 @@ const rehypePrismGenerator = (refractor) => {
       }
 
       let lang = getLanguage(node)
-      // If no language is set on the code block, use defaultLanguage if specified
       if (!lang && options.defaultLanguage) {
         lang = options.defaultLanguage
         node.properties.className.push(`language-${lang}`)
       }
-      node.properties.className.push('code-highlight')
+      node.properties.className.push("code-highlight")
 
       /** @type {Element} */
       let refractorRoot
@@ -215,8 +289,8 @@ const rehypePrismGenerator = (refractor) => {
       if (lang) {
         try {
           let rootLang
-          if (lang?.includes('diff-')) {
-            rootLang = lang.split('-')[1]
+          if (lang?.includes("diff-")) {
+            rootLang = lang.split("-")[1]
           } else {
             rootLang = lang
           }
@@ -224,7 +298,7 @@ const rehypePrismGenerator = (refractor) => {
           refractorRoot = refractor.highlight(toString(node), rootLang)
           // @ts-ignore className is already an array
           parent.properties.className = (parent.properties.className || []).concat(
-            'language-' + rootLang
+            "language-" + rootLang
           )
         } catch (err) {
           if (options.ignoreMissing && /Unknown language/.test(err.message)) {
@@ -260,13 +334,18 @@ const rehypePrismGenerator = (refractor) => {
       const codeLineArray = createLineNodes(refractorRoot.position.end.line)
 
       const falseShowLineNumbersStr = [
-        'showlinenumbers=false',
-        'showlinenumbers="false"',
-        'showlinenumbers={false}',
+        "showlinenumbers=false",
+        "showlinenumbers=\"false\"",
+        "showlinenumbers={false}",
       ]
+      const shouldShowLineNumbers = (meta.toLowerCase().includes("showLineNumbers".toLowerCase())
+        || options.showLineNumbers) && !falseShowLineNumbersStr.some((str) => meta.toLowerCase().includes(str))
+
+      createToolbarElement(parent, meta)
+
       for (const [i, line] of codeLineArray.entries()) {
         // Default class name for each line
-        line.properties.className = ['code-line']
+        line.properties.className = ["code-line"]
 
         // Syntax highlight
         const treeExtract = filter(
@@ -275,39 +354,33 @@ const rehypePrismGenerator = (refractor) => {
         )
         line.children = treeExtract.children
 
-        // Line number
-        if (
-          (meta.toLowerCase().includes('showLineNumbers'.toLowerCase()) ||
-            options.showLineNumbers) &&
-          !falseShowLineNumbersStr.some((str) => meta.toLowerCase().includes(str))
-        ) {
+        if (shouldShowLineNumbers) {
           line.properties.line = [(i + startingLineNumber).toString()]
-          line.properties.className.push('line-number')
+          line.properties.className.push("line-number")
         }
 
-        // Line highlight
         if (shouldHighlightLine(i)) {
-          line.properties.className.push('highlight-line')
+          line.properties.className.push("highlight-line")
         }
 
         // Diff classes
         if (
-          (lang === 'diff' || lang?.includes('diff-')) &&
-          toString(line).substring(0, 1) === '-'
+          (lang === "diff" || lang?.includes("diff-")) &&
+          toString(line).substring(0, 1) === "-"
         ) {
-          line.properties.className.push('deleted')
+          line.properties.className.push("deleted")
         } else if (
-          (lang === 'diff' || lang?.includes('diff-')) &&
-          toString(line).substring(0, 1) === '+'
+          (lang === "diff" || lang?.includes("diff-")) &&
+          toString(line).substring(0, 1) === "+"
         ) {
-          line.properties.className.push('inserted')
+          line.properties.className.push("inserted")
         }
       }
 
       // Remove possible trailing line when splitting by \n which results in empty array
       if (
         codeLineArray.length > 0 &&
-        toString(codeLineArray[codeLineArray.length - 1]).trim() === ''
+        toString(codeLineArray[codeLineArray.length - 1]).trim() === ""
       ) {
         codeLineArray.pop()
       }