@diplodoc/transform 4.68.3 → 4.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,44 +1,59 @@
1
1
  "use strict";
2
2
  /* eslint-disable max-len */
3
3
  const utils_1 = require("./utils");
4
- const wrapInClipboard = (element, id) => {
4
+ const wrapInFloatingContainer = (element, id, { lineWrapping }) => {
5
+ const wrappingButton = lineWrapping
6
+ ? `<button role="button" class="yfm-code-button yfm-wrapping-button" aria-label="Toggle line wrapping">
7
+ <svg width="16" height="16" viewBox="0 0 16 16" class="yfm-code-icon yfm-wrapping-icon" fill="none" xmlns="http://www.w3.org/2000/svg">
8
+ <path
9
+ fill="currentColor"
10
+ fill-rule="evenodd"
11
+ clip-rule="evenodd"
12
+ d="M2.47 11.28a.75.75 0 0 1 0-1.06l3-3a.75.75 0 0 1 1.06 1.06L4.81 10H9a3.25 3.25 0 0 0 0-6.5H8A.75.75 0 0 1 8 2h1a4.75 4.75 0 1 1 0 9.5H4.81l1.72 1.72a.75.75 0 1 1-1.06 1.06z"
13
+ />
14
+ </svg>
15
+ </button>`
16
+ : '';
5
17
  return `
6
- <div class="yfm-clipboard">
18
+ <div class="yfm-code-floating-container">
7
19
  ${element}
8
- <button class="yfm-clipboard-button" aria-label="Copy">
9
- <svg width="16" height="16" viewBox="0 0 24 24" class="yfm-clipboard-icon" data-animation="${id}">
10
- <path
11
- fill="currentColor"
12
- d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
13
- />
14
- <path
15
- stroke="currentColor"
16
- fill="transparent"
17
- stroke-width="1.5"
18
- d="M9.5 13l3 3l5 -5"
19
- visibility="hidden"
20
- >
21
- <animate
22
- id="visibileAnimation-${id}"
23
- attributeName="visibility"
24
- from="hidden"
25
- to="visible"
26
- dur="0.2s"
27
- fill="freeze"
28
- begin=""
29
- />
30
- <animate
31
- id="hideAnimation-${id}"
32
- attributeName="visibility"
33
- from="visible"
34
- to="hidden"
35
- dur="1s"
36
- begin="visibileAnimation-${id}.end+1"
37
- fill="freeze"
20
+ <div class="yfm-code-floating">
21
+ ${wrappingButton}
22
+ <button role="button" class="yfm-code-button yfm-clipboard-button" aria-label="Copy">
23
+ <svg width="16" height="16" viewBox="0 0 24 24" class="yfm-code-icon yfm-clipboard-icon" data-animation="${id}">
24
+ <path
25
+ fill="currentColor"
26
+ d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
38
27
  />
39
- </path>
40
- </svg>
41
- </button>
28
+ <path
29
+ stroke="currentColor"
30
+ fill="transparent"
31
+ stroke-width="1.5"
32
+ d="M9.5 13l3 3l5 -5"
33
+ visibility="hidden"
34
+ >
35
+ <animate
36
+ id="visibileAnimation-${id}"
37
+ attributeName="visibility"
38
+ from="hidden"
39
+ to="visible"
40
+ dur="0.2s"
41
+ fill="freeze"
42
+ begin=""
43
+ />
44
+ <animate
45
+ id="hideAnimation-${id}"
46
+ attributeName="visibility"
47
+ from="visible"
48
+ to="hidden"
49
+ dur="1s"
50
+ begin="visibileAnimation-${id}.end+1"
51
+ fill="freeze"
52
+ />
53
+ </path>
54
+ </svg>
55
+ </button>
56
+ </div>
42
57
  </div>
43
58
  `;
44
59
  };
@@ -52,7 +67,7 @@ function termReplace(str, env, escape) {
52
67
  const termCode = str.replace(reg, (_match, p1, _p2, p3) => `<i class="yfm yfm-term_title" term-key=":${p3}" id="${(0, utils_1.generateID)()}">${p1}</i>`);
53
68
  return termCode || str;
54
69
  }
55
- function addLineNumbers(code) {
70
+ function addLineNumbers(code, { lineWrapping }) {
56
71
  const lines = code.split('\n');
57
72
  const lineCount = lines.length;
58
73
  const maxDigits = String(lineCount).length;
@@ -62,11 +77,14 @@ function addLineNumbers(code) {
62
77
  return (linesToProcess
63
78
  .map((line, index) => {
64
79
  const lineNumber = String(index + 1).padStart(maxDigits, ' ');
65
- return `<span class="yfm-line-number">${lineNumber}</span>${line}`;
80
+ return lineWrapping
81
+ ? `<span class="yfm-line-number">${lineNumber}</span><span class="yfm-line">${line}</span>`
82
+ : `<span class="yfm-line-number">${lineNumber}</span>${line}`;
66
83
  })
67
84
  .join('\n') + (hasTrailingNewline ? '\n' : ''));
68
85
  }
69
- const code = (md) => {
86
+ const code = (md, opts) => {
87
+ const lineWrapping = (opts === null || opts === void 0 ? void 0 : opts.codeLineWrapping) || false;
70
88
  const superCodeRenderer = md.renderer.rules.fence;
71
89
  md.renderer.rules.fence = function (tokens, idx, options, env, self) {
72
90
  const token = tokens[idx];
@@ -77,12 +95,12 @@ const code = (md) => {
77
95
  const codeMatch = superCode.match(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/);
78
96
  if (codeMatch) {
79
97
  const codeContent = codeMatch[1];
80
- const codeWithLineNumbers = addLineNumbers(codeContent);
98
+ const codeWithLineNumbers = addLineNumbers(codeContent, { lineWrapping });
81
99
  superCode = superCode.replace(codeContent, codeWithLineNumbers);
82
100
  }
83
101
  }
84
102
  const superCodeWithTerms = superCode && (env === null || env === void 0 ? void 0 : env.terms) ? termReplace(superCode, env, md.utils.escapeRE) : superCode;
85
- return wrapInClipboard(superCodeWithTerms, idx);
103
+ return wrapInFloatingContainer(superCodeWithTerms, idx, { lineWrapping });
86
104
  };
87
105
  };
88
106
  module.exports = code;
@@ -1 +1 @@
1
- {"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/transform/plugins/code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/transform/plugins/code.ts"],"names":[],"mappings":"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"}
package/lib/typings.d.ts CHANGED
@@ -72,6 +72,12 @@ export interface OptionsType {
72
72
  rawContent?: (path: string, assets: Record<string, string | boolean>) => string | boolean;
73
73
  calcPath?: (root: string, path: string) => string;
74
74
  replaceImageSrc?: (state: StateCore, currentPath: string, path: string, imgSrc: string, opts: unknown) => string;
75
+ /**
76
+ * Show button to toggle line wrapping of code.
77
+ * @type {boolean}
78
+ * @default false
79
+ */
80
+ codeLineWrapping?: boolean;
75
81
  }
76
82
  export interface OutputType {
77
83
  result: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diplodoc/transform",
3
- "version": "4.68.3",
3
+ "version": "4.69.1",
4
4
  "description": "A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML",
5
5
  "keywords": [
6
6
  "markdown",
@@ -41,7 +41,8 @@
41
41
  "playground": "run-p dev:*",
42
42
  "artifact:playground": "node ./playground/scripts/pages.mjs",
43
43
  "prepublishOnly": "npm run lint && npm run test && npm run build",
44
- "test": "vitest run --coverage",
44
+ "test": "vitest run",
45
+ "test:coverage": "vitest run --coverage",
45
46
  "test:playwright:nobuild": "cd e2e && npm run playwright:docker --",
46
47
  "test:playwright": "npm run build && npm run test:playwright:nobuild --",
47
48
  "typecheck": "tsc -p tsconfig.json --noEmit",
@@ -62,7 +63,7 @@
62
63
  "get-root-node-polyfill": "1.0.0",
63
64
  "github-slugger": "^1.5.0",
64
65
  "js-yaml": "^4.1.0",
65
- "lodash": "4.17.21",
66
+ "lodash": "^4.17.23",
66
67
  "markdown-it": "^13.0.2",
67
68
  "markdown-it-attrs": "^4.2.0",
68
69
  "markdown-it-deflist": "2.1.0",
@@ -77,12 +78,12 @@
77
78
  },
78
79
  "devDependencies": {
79
80
  "@diplodoc/babel-preset": "^1.0.2",
80
- "@diplodoc/lint": "^1.10.2",
81
+ "@diplodoc/lint": "^1.13.2",
81
82
  "@diplodoc/tsconfig": "^1.0.2",
82
83
  "@types/css": "0.0.34",
83
84
  "@types/github-slugger": "^1.3.0",
84
85
  "@types/js-yaml": "^4.0.5",
85
- "@types/lodash": "4.14.183",
86
+ "@types/lodash": "^4.17.23",
86
87
  "@types/markdown-it": "^13.0.9",
87
88
  "@types/markdown-it-attrs": "4.1.0",
88
89
  "@types/node": "^22.19.7",
package/src/js/code.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {copyToClipboard, getEventTarget, isCustom} from './utils';
2
2
 
3
- const BUTTON_SELECTOR = '.yfm-clipboard-button';
3
+ const COPY_BUTTON_SELECTOR = '.yfm-clipboard-button';
4
+ const WRAP_BUTTON_SELECTOR = '.yfm-wrapping-button';
4
5
 
5
6
  function notifySuccess(svgButton: HTMLElement | null) {
6
7
  if (!svgButton) {
@@ -20,15 +21,9 @@ function notifySuccess(svgButton: HTMLElement | null) {
20
21
  }
21
22
 
22
23
  function buttonCopyFn(target: HTMLElement) {
23
- const parent = target.parentNode;
24
-
25
- if (!parent) {
26
- return;
27
- }
28
-
29
- const code = parent.querySelector<HTMLElement>('pre code');
30
-
31
- if (!code) {
24
+ const container = target.parentNode?.parentNode;
25
+ const code = container?.querySelector<HTMLElement>('pre code');
26
+ if (!container || !code) {
32
27
  return;
33
28
  }
34
29
 
@@ -45,22 +40,36 @@ function buttonCopyFn(target: HTMLElement) {
45
40
  .join('');
46
41
 
47
42
  copyToClipboard(textContent.trim()).then(() => {
48
- notifySuccess(parent.querySelector('.yfm-clipboard-icon'));
43
+ notifySuccess(container.querySelector('.yfm-clipboard-icon'));
49
44
 
50
45
  setTimeout(() => target.blur(), 1500);
51
46
  });
52
47
  }
53
48
 
54
- if (typeof document !== 'undefined') {
55
- document.addEventListener('click', (event) => {
56
- const target = getEventTarget(event) as HTMLElement;
49
+ function buttonWrapFn(target: HTMLElement) {
50
+ const container = target.parentNode?.parentNode;
51
+ const code = container?.querySelector<HTMLElement>('pre code');
52
+ if (!container || !code) {
53
+ return;
54
+ }
55
+
56
+ code.classList.toggle('wrap');
57
57
 
58
- const button = target.matches(BUTTON_SELECTOR);
58
+ setTimeout(() => target.blur(), 500);
59
+ }
59
60
 
60
- if (isCustom(event) || !button) {
61
+ if (typeof document !== 'undefined') {
62
+ document.addEventListener('click', (event) => {
63
+ if (isCustom(event)) {
61
64
  return;
62
65
  }
63
66
 
64
- buttonCopyFn(target);
67
+ const target = getEventTarget(event) as HTMLElement;
68
+
69
+ if (target.matches(COPY_BUTTON_SELECTOR)) {
70
+ buttonCopyFn(target);
71
+ } else if (target.matches(WRAP_BUTTON_SELECTOR)) {
72
+ buttonWrapFn(target);
73
+ }
65
74
  });
66
75
  }
@@ -1,14 +1,23 @@
1
1
  .yfm {
2
- &-clipboard {
2
+ &-code-floating-container {
3
3
  position: relative;
4
4
 
5
- .yfm-clipboard-button {
5
+ .yfm-code-button {
6
6
  min-width: 20px;
7
7
  min-height: 20px;
8
+ flex-shrink: 0;
9
+
10
+ //reset default button style
11
+ background: none;
12
+ color: inherit;
13
+ border: none;
14
+ padding: 0;
15
+ font: inherit;
16
+ cursor: pointer;
8
17
  }
9
18
 
10
19
  &:hover {
11
- .yfm-clipboard-button {
20
+ .yfm-code-floating {
12
21
  opacity: 1;
13
22
  }
14
23
  }
@@ -16,42 +25,50 @@
16
25
  & > pre {
17
26
  border-radius: 10px;
18
27
  overflow: hidden;
28
+
29
+ // codeblock has line-wrapping and line-numbers
30
+ code:has(> .yfm-line) {
31
+ display: grid;
32
+ grid-template-columns: auto 1fr;
33
+ }
34
+
35
+ code.wrap {
36
+ white-space: pre-wrap;
37
+ }
19
38
  }
20
39
 
21
- &-button {
40
+ .yfm-code-floating {
22
41
  position: absolute;
23
42
  top: 16px;
24
43
  right: 16px;
25
44
  z-index: 1;
45
+
26
46
  opacity: 0;
27
47
 
28
- //reset default button style
29
- background: none;
30
- color: inherit;
31
- border: none;
32
- padding: 0;
33
- font: inherit;
34
- cursor: pointer;
48
+ display: flex;
49
+ flex-wrap: nowrap;
50
+ gap: 4px;
35
51
 
36
- &:focus {
52
+ &:focus,
53
+ &:has(> .yfm-code-button:focus) {
37
54
  opacity: 1;
38
55
  }
39
56
  }
40
57
 
41
- &-icon {
58
+ .yfm-code-icon {
42
59
  pointer-events: none;
43
60
  }
61
+ }
44
62
 
45
- &-inline-code {
46
- transition: 0.3s;
63
+ &-clipboard-inline-code {
64
+ transition: 0.3s;
47
65
 
48
- &:hover {
49
- cursor: pointer;
50
- background-color: var(
51
- --yfm-color-code-hovered-background,
52
- var(--yfm-color-code-background-hovered-private)
53
- );
54
- }
66
+ &:hover {
67
+ cursor: pointer;
68
+ background-color: var(
69
+ --yfm-color-code-hovered-background,
70
+ var(--yfm-color-code-background-hovered-private)
71
+ );
55
72
  }
56
73
  }
57
74
  }
@@ -276,9 +276,9 @@
276
276
 
277
277
  hr {
278
278
  box-sizing: content-box;
279
- height: 0.25em;
279
+ height: var(--yfm-hr-height, 0.25em);
280
280
  padding: 0;
281
- margin: 1.5em 0;
281
+ margin: var(--yfm-hr-margin, 1.5em 0);
282
282
  overflow: hidden;
283
283
 
284
284
  background-color: var(--yfm-color-border, var(--yfm-color-border-private));
@@ -1,6 +1,6 @@
1
1
  @media print {
2
2
  .yfm {
3
- .yfm-clipboard {
3
+ .yfm-code-floating-container {
4
4
  & > pre > code {
5
5
  white-space: pre-wrap;
6
6
  }
@@ -4,44 +4,64 @@ import type {MarkdownItPluginCb} from './typings';
4
4
 
5
5
  import {generateID} from './utils';
6
6
 
7
- const wrapInClipboard = (element: string | undefined, id: number) => {
7
+ const wrapInFloatingContainer = (
8
+ element: string | undefined,
9
+ id: number,
10
+ {lineWrapping}: {lineWrapping: boolean},
11
+ ) => {
12
+ const wrappingButton = lineWrapping
13
+ ? `<button role="button" class="yfm-code-button yfm-wrapping-button" aria-label="Toggle line wrapping">
14
+ <svg width="16" height="16" viewBox="0 0 16 16" class="yfm-code-icon yfm-wrapping-icon" fill="none" xmlns="http://www.w3.org/2000/svg">
15
+ <path
16
+ fill="currentColor"
17
+ fill-rule="evenodd"
18
+ clip-rule="evenodd"
19
+ d="M2.47 11.28a.75.75 0 0 1 0-1.06l3-3a.75.75 0 0 1 1.06 1.06L4.81 10H9a3.25 3.25 0 0 0 0-6.5H8A.75.75 0 0 1 8 2h1a4.75 4.75 0 1 1 0 9.5H4.81l1.72 1.72a.75.75 0 1 1-1.06 1.06z"
20
+ />
21
+ </svg>
22
+ </button>`
23
+ : '';
24
+
8
25
  return `
9
- <div class="yfm-clipboard">
26
+ <div class="yfm-code-floating-container">
10
27
  ${element}
11
- <button class="yfm-clipboard-button" aria-label="Copy">
12
- <svg width="16" height="16" viewBox="0 0 24 24" class="yfm-clipboard-icon" data-animation="${id}">
13
- <path
14
- fill="currentColor"
15
- d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
16
- />
17
- <path
18
- stroke="currentColor"
19
- fill="transparent"
20
- stroke-width="1.5"
21
- d="M9.5 13l3 3l5 -5"
22
- visibility="hidden"
23
- >
24
- <animate
25
- id="visibileAnimation-${id}"
26
- attributeName="visibility"
27
- from="hidden"
28
- to="visible"
29
- dur="0.2s"
30
- fill="freeze"
31
- begin=""
32
- />
33
- <animate
34
- id="hideAnimation-${id}"
35
- attributeName="visibility"
36
- from="visible"
37
- to="hidden"
38
- dur="1s"
39
- begin="visibileAnimation-${id}.end+1"
40
- fill="freeze"
28
+ <div class="yfm-code-floating">
29
+ ${wrappingButton}
30
+ <button role="button" class="yfm-code-button yfm-clipboard-button" aria-label="Copy">
31
+ <svg width="16" height="16" viewBox="0 0 24 24" class="yfm-code-icon yfm-clipboard-icon" data-animation="${id}">
32
+ <path
33
+ fill="currentColor"
34
+ d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
41
35
  />
42
- </path>
43
- </svg>
44
- </button>
36
+ <path
37
+ stroke="currentColor"
38
+ fill="transparent"
39
+ stroke-width="1.5"
40
+ d="M9.5 13l3 3l5 -5"
41
+ visibility="hidden"
42
+ >
43
+ <animate
44
+ id="visibileAnimation-${id}"
45
+ attributeName="visibility"
46
+ from="hidden"
47
+ to="visible"
48
+ dur="0.2s"
49
+ fill="freeze"
50
+ begin=""
51
+ />
52
+ <animate
53
+ id="hideAnimation-${id}"
54
+ attributeName="visibility"
55
+ from="visible"
56
+ to="hidden"
57
+ dur="1s"
58
+ begin="visibileAnimation-${id}.end+1"
59
+ fill="freeze"
60
+ />
61
+ </path>
62
+ </svg>
63
+ </button>
64
+ </div>
45
65
  </div>
46
66
  `;
47
67
  };
@@ -69,7 +89,7 @@ function termReplace(str: string, env: EnvTerm, escape: (str: string) => string)
69
89
  return termCode || str;
70
90
  }
71
91
 
72
- function addLineNumbers(code: string): string {
92
+ function addLineNumbers(code: string, {lineWrapping}: {lineWrapping: boolean}): string {
73
93
  const lines = code.split('\n');
74
94
  const lineCount = lines.length;
75
95
  const maxDigits = String(lineCount).length;
@@ -82,13 +102,26 @@ function addLineNumbers(code: string): string {
82
102
  linesToProcess
83
103
  .map((line, index) => {
84
104
  const lineNumber = String(index + 1).padStart(maxDigits, ' ');
85
- return `<span class="yfm-line-number">${lineNumber}</span>${line}`;
105
+ return lineWrapping
106
+ ? `<span class="yfm-line-number">${lineNumber}</span><span class="yfm-line">${line}</span>`
107
+ : `<span class="yfm-line-number">${lineNumber}</span>${line}`;
86
108
  })
87
109
  .join('\n') + (hasTrailingNewline ? '\n' : '')
88
110
  );
89
111
  }
90
112
 
91
- const code: MarkdownItPluginCb = (md) => {
113
+ type CodeOptions = {
114
+ /**
115
+ * Show button to toggle line wrapping of code.
116
+ * @type {boolean}
117
+ * @default false
118
+ */
119
+ codeLineWrapping?: boolean;
120
+ };
121
+
122
+ const code: MarkdownItPluginCb<CodeOptions> = (md, opts) => {
123
+ const lineWrapping = opts?.codeLineWrapping || false;
124
+
92
125
  const superCodeRenderer = md.renderer.rules.fence;
93
126
  md.renderer.rules.fence = function (tokens, idx, options, env, self) {
94
127
  const token = tokens[idx];
@@ -101,7 +134,7 @@ const code: MarkdownItPluginCb = (md) => {
101
134
  const codeMatch = superCode.match(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/);
102
135
  if (codeMatch) {
103
136
  const codeContent = codeMatch[1];
104
- const codeWithLineNumbers = addLineNumbers(codeContent);
137
+ const codeWithLineNumbers = addLineNumbers(codeContent, {lineWrapping});
105
138
  superCode = superCode.replace(codeContent, codeWithLineNumbers);
106
139
  }
107
140
  }
@@ -109,7 +142,7 @@ const code: MarkdownItPluginCb = (md) => {
109
142
  const superCodeWithTerms =
110
143
  superCode && env?.terms ? termReplace(superCode, env, md.utils.escapeRE) : superCode;
111
144
 
112
- return wrapInClipboard(superCodeWithTerms, idx);
145
+ return wrapInFloatingContainer(superCodeWithTerms, idx, {lineWrapping});
113
146
  };
114
147
  };
115
148
 
@@ -103,6 +103,12 @@ export interface OptionsType {
103
103
  imgSrc: string,
104
104
  opts: unknown,
105
105
  ) => string;
106
+ /**
107
+ * Show button to toggle line wrapping of code.
108
+ * @type {boolean}
109
+ * @default false
110
+ */
111
+ codeLineWrapping?: boolean;
106
112
  }
107
113
 
108
114
  export interface OutputType {