diff --git a/package-lock.json b/package-lock.json index cf208577..f6235b73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "css": "^3.0.0", "cssfilter": "0.0.10", "get-root-node-polyfill": "1.0.0", + "github-slugger": "^1.5.0", "js-yaml": "^4.1.0", "lodash": "4.17.21", "markdown-it": "^13.0.2", @@ -25,7 +26,8 @@ "markdownlint": "^0.25.1", "markdownlint-rule-helpers": "0.17.2", "sanitize-html": "^2.11.0", - "slugify": "1.6.5" + "slugify": "1.6.5", + "svgo": "^3.2.0" }, "devDependencies": { "@diplodoc/babel-preset": "^1.0.2", @@ -45,7 +47,6 @@ "autoprefixer": "^10.4.15", "esbuild": "^0.19.2", "esbuild-sass-plugin": "^2.12.0", - "github-slugger": "^1.5.0", "highlight.js": "^11.8.0", "jest": "28.1.3", "markdown-it-testgen": "^0.1.6", @@ -3767,6 +3768,14 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5171,6 +5180,14 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5308,6 +5325,18 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -5352,6 +5381,36 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -6727,8 +6786,7 @@ "node_modules/github-slugger": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", - "dev": true + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" }, "node_modules/glob": { "version": "7.2.3", @@ -8467,6 +8525,11 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -10477,6 +10540,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svgo": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "node_modules/synckit": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", diff --git a/package.json b/package.json index 9747e916..77aeedf8 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,26 @@ "name": "@diplodoc/transform", "version": "4.12.0", "description": "A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML", - "author": "YFM Team ", - "license": "MIT", + "keywords": [ + "markdown", + "yandex", + "docs", + "yfm", + "documentation", + "tool", + "tools", + "generator" + ], + "homepage": "https://github.com/diplodoc-platform/transform#readme", + "bugs": { + "url": "https://github.com/diplodoc-platform/transform/issues" + }, "repository": { "type": "git", "url": "git@github.com:diplodoc-platform/transform.git" }, + "license": "MIT", + "author": "YFM Team ", "main": "lib/index.js", "files": [ "dist", @@ -16,14 +30,14 @@ ], "scripts": { "build": "npm run build:lib && npm run build:dist", - "build:lib": "tsc -p tsconfig.transform.json", "build:dist": "./esbuild/build.js", - "test": "jest --coverage", + "build:lib": "tsc -p tsconfig.transform.json", "lint": "eslint --max-warnings=0 \"{src,test}/**/*.{js,jsx,ts,tsx}\"", "lint:fix": "eslint --fix --max-warnings=0 \"{src,test}/**/*.{js,jsx,ts,tsx}\"", - "typecheck": "tsc -p tsconfig.json --noEmit", "precommit": "npm run lint && npm run test", - "prepublishOnly": "npm run lint && npm run test && npm run build" + "prepublishOnly": "npm run lint && npm run test && npm run build", + "test": "jest --coverage", + "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { "@diplodoc/tabs-extension": "^2.1.0", @@ -32,6 +46,7 @@ "css": "^3.0.0", "cssfilter": "0.0.10", "get-root-node-polyfill": "1.0.0", + "github-slugger": "^1.5.0", "js-yaml": "^4.1.0", "lodash": "4.17.21", "markdown-it": "^13.0.2", @@ -42,7 +57,8 @@ "markdownlint": "^0.25.1", "markdownlint-rule-helpers": "0.17.2", "sanitize-html": "^2.11.0", - "slugify": "1.6.5" + "slugify": "1.6.5", + "svgo": "^3.2.0" }, "devDependencies": { "@diplodoc/babel-preset": "^1.0.2", @@ -62,7 +78,6 @@ "autoprefixer": "^10.4.15", "esbuild": "^0.19.2", "esbuild-sass-plugin": "^2.12.0", - "github-slugger": "^1.5.0", "highlight.js": "^11.8.0", "jest": "28.1.3", "markdown-it-testgen": "^0.1.6", @@ -79,20 +94,6 @@ "optional": true } }, - "bugs": { - "url": "https://github.com/diplodoc-platform/transform/issues" - }, - "homepage": "https://github.com/diplodoc-platform/transform#readme", - "keywords": [ - "markdown", - "yandex", - "docs", - "yfm", - "documentation", - "tool", - "tools", - "generator" - ], "moduleDirectories": [ "node_modules", "src" diff --git a/src/transform/plugins/images/index.ts b/src/transform/plugins/images/index.ts index 5767303b..2b7fa574 100644 --- a/src/transform/plugins/images/index.ts +++ b/src/transform/plugins/images/index.ts @@ -1,12 +1,13 @@ -import {readFileSync} from 'fs'; import {join, sep} from 'path'; import {bold} from 'chalk'; +import {optimize} from 'svgo'; import {isFileExists, resolveRelativePath} from '../../utilsFS'; import {isExternalHref, isLocalUrl} from '../../utils'; import Token from 'markdown-it/lib/token'; import {MarkdownItPluginCb, MarkdownItPluginOpts} from '../typings'; import {StateCore} from '../../typings'; +import {readFileSync} from 'fs'; interface ImageOpts extends MarkdownItPluginOpts { assetsPublicPath: string; @@ -43,6 +44,12 @@ interface SVGOpts extends MarkdownItPluginOpts { notFoundCb: (s: string) => void; } +function prefix() { + const value = Math.floor(Math.random() * 1e9); + + return value.toString(16); +} + function convertSvg( token: Token, state: StateCore, @@ -52,12 +59,24 @@ function convertSvg( const path = resolveRelativePath(currentPath, token.attrGet('src') || ''); try { - const content = readFileSync(path, 'utf8'); + const raw = readFileSync(path).toString(); + const result = optimize(raw, { + plugins: [ + { + name: 'prefixIds', + params: { + prefix: prefix(), + }, + }, + ], + }); + + const content = result.data; const svgToken = new state.Token('image_svg', '', 0); svgToken.attrSet('content', content); return svgToken; - } catch (e) { + } catch (e: unknown) { log.error(`SVG ${path} from ${currentPath} not found`); if (notFoundCb) { diff --git a/src/transform/sanitize.ts b/src/transform/sanitize.ts index 392de541..e889f060 100644 --- a/src/transform/sanitize.ts +++ b/src/transform/sanitize.ts @@ -475,6 +475,8 @@ const svgAttrs = [ 'zoomandpan', 'from', 'to', + 'xlink:href', + 'use', ]; const defaultCssWhitelist = {