@brillout/docpress 0.15.10-commit-af2d9bc → 0.15.10-commit-f89d08f

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,3 +1,15 @@
1
+ /* Wrapper */
2
+ .code-snippet,
3
+ .code-snippets {
4
+ position: relative;
5
+ &:hover {
6
+ button,
7
+ select {
8
+ opacity: 1;
9
+ }
10
+ }
11
+ }
12
+
1
13
  /* Language select */
2
14
  .code-lang-select {
3
15
  right: 42px;
@@ -41,7 +53,9 @@
41
53
  border: 1px solid #ccc;
42
54
  border-radius: 5px;
43
55
  background-color: #f7f7f7;
44
- &:hover {
56
+ opacity: 0;
57
+ transition: opacity 0.8s ease-in-out, background-color 0.4s ease-in-out;
58
+ &:not(:hover) {
45
59
  background-color: #eee;
46
60
  }
47
61
  }
@@ -18,7 +18,7 @@ function TypescriptOnly({ children }: { children: React.ReactNode }) {
18
18
  function CodeSnippets({ children }: { children: React.ReactNode }) {
19
19
  const [codeLangSelected, selectCodeLang] = useSelectCodeLang()
20
20
  return (
21
- <div>
21
+ <div className="code-snippets">
22
22
  <form style={{ position: 'relative' }}>
23
23
  <select className="code-lang-select" onChange={onChange} value={codeLangSelected}>
24
24
  <option value="js">JavaScript</option>
@@ -43,7 +43,7 @@ function CodeSnippet({
43
43
  const displayStyle = tsOnly ? {} : { display: codeLangSelected === codeLang ? 'block' : 'none' }
44
44
 
45
45
  return (
46
- <div style={{ ...displayStyle, position: 'relative' }}>
46
+ <div className="code-snippet" style={{ ...displayStyle }}>
47
47
  <CopyButton />
48
48
  {children}
49
49
  </div>
package/detypePlugin.ts CHANGED
@@ -4,6 +4,7 @@ import type { PluginOption } from 'vite'
4
4
  import module from 'node:module'
5
5
  import { assertUsage } from './utils/assert.js'
6
6
  import pc from '@brillout/picocolors'
7
+ import { getMagicString } from './utils/getMagicString.js'
7
8
  // Cannot use `import { transform } from 'detype'` as it results in errors,
8
9
  // and the package has no default export. Using `module.createRequire` instead.
9
10
  const { transform: detype } = module.createRequire(import.meta.url)('detype') as typeof import('detype')
@@ -32,7 +33,7 @@ const prettierOptions: NonNullable<Parameters<typeof detype>[2]>['prettierOption
32
33
  // > const hello: string = 'world'
33
34
  // > ```
34
35
  // ~~~
35
- const codeBlockRE = /^(.*)```(tsx?|vue)[^\n]*\n([\s\S]*?)```/gm
36
+ const codeBlockRE = /^(.*)```(tsx?|vue|yaml)[^\n]*\n([\s\S]*?)```/gm
36
37
 
37
38
  function detypePlugin(): PluginOption {
38
39
  return {
@@ -40,8 +41,7 @@ function detypePlugin(): PluginOption {
40
41
  enforce: 'pre',
41
42
  transform: async (code: string, moduleId: string) => {
42
43
  if (!moduleId.endsWith('.mdx')) return
43
- const codeNew = await transformCode(code, moduleId)
44
- return codeNew
44
+ return await transformCode(code, moduleId)
45
45
  },
46
46
  }
47
47
  }
@@ -50,64 +50,86 @@ async function transformCode(code: string, moduleId: string) {
50
50
  const matches = Array.from(code.matchAll(codeBlockRE))
51
51
  if (matches.length === 0) return
52
52
 
53
- let codeNew = `import { CodeSnippets, CodeSnippet } from '@brillout/docpress';\n\n`
54
- let lastIndex = 0
53
+ const { magicString, getMagicStringResult } = getMagicString(code, moduleId)
55
54
 
56
- for (const match of matches) {
55
+ magicString.prepend(`import { CodeSnippets, CodeSnippet } from '@brillout/docpress';\n\n`)
56
+
57
+ // [Claude AI] Process matches in reverse order to avoid offset issues
58
+ for (let i = matches.length - 1; i >= 0; i--) {
59
+ const match = matches[i]
57
60
  const [codeBlockOuterStr, codeBlockIndent, codeBlockLang, codeBlockContentWithIndent] = match
61
+ const isYaml = codeBlockLang === 'yaml'
58
62
 
59
63
  // Remove indentation
60
64
  const codeBlockOpen = codeBlockOuterStr.split('\n')[0].slice(codeBlockIndent.length)
61
65
  const codeBlockContent = removeCodeBlockIndent(codeBlockContentWithIndent, codeBlockIndent, moduleId)
62
66
 
63
- const blockStartIndex = match.index
64
- const blockEndIndex = blockStartIndex + codeBlockOuterStr.length
65
- codeNew += code.slice(lastIndex, blockStartIndex)
66
-
67
- if (codeBlockOpen.includes('ts-only')) {
68
- codeNew += `${codeBlockIndent}<CodeSnippet codeLang="ts" tsOnly>\n${codeBlockOuterStr}\n${codeBlockIndent}</CodeSnippet>`
67
+ let replacement: string
68
+ if (codeBlockOpen.includes('ts-only') && !isYaml) {
69
+ replacement = `${codeBlockIndent}<CodeSnippet codeLang="ts" tsOnly>\n${codeBlockOuterStr}\n${codeBlockIndent}</CodeSnippet>`
69
70
  } else {
70
71
  // someFileName.ts => someFileName.js
71
72
  let codeBlockContentJs = codeBlockContent.replaceAll('.ts', '.js')
73
+ const codeBlockClose = '```'
74
+ if (isYaml && codeBlockContentJs === codeBlockContent) {
75
+ continue
76
+ }
77
+
72
78
  // Remove TypeScript
73
- codeBlockContentJs = await detype(codeBlockContentJs, `some-dummy-filename.${codeBlockLang}`, {
74
- removeTsComments: true,
75
- prettierOptions,
76
- })
79
+ if (!isYaml) {
80
+ codeBlockContentJs = await detype(codeBlockContentJs, `some-dummy-filename.${codeBlockLang}`, {
81
+ removeTsComments: true,
82
+ prettierOptions,
83
+ })
84
+ }
77
85
 
86
+ // Update code block open delimiter
78
87
  const codeBlockLangJs =
79
88
  codeBlockLang === 'vue'
80
89
  ? 'vue'
81
90
  : // ts => js | tsx => jsx
82
91
  codeBlockLang.replace('t', 'j')
83
92
  const codeBlockOpenJs = codeBlockOpen.replace(codeBlockLang, codeBlockLangJs)
84
- const codeBlockClose = '```'
85
93
 
86
- const codeSnippetTs = `<CodeSnippet codeLang="ts">\n${codeBlockOpen}\n${codeBlockContent}${codeBlockClose}\n</CodeSnippet>`
87
- const codeSnippetJs = `<CodeSnippet codeLang="js">\n${codeBlockOpenJs}\n${codeBlockContentJs}${codeBlockClose}\n</CodeSnippet>`
88
- const codeSnippets = restoreCodeBlockIndent(
89
- `<CodeSnippets>\n${codeSnippetJs}\n${codeSnippetTs}\n</CodeSnippets>`,
90
- codeBlockIndent,
91
- )
94
+ // Wrap each with <CodeSnippet>
95
+ let codeSnippets = [
96
+ wrapCodeSnippet('ts', `${codeBlockOpen}\n${codeBlockContent}${codeBlockClose}`),
97
+ wrapCodeSnippet('js', `${codeBlockOpenJs}\n${codeBlockContentJs}${codeBlockClose}`),
98
+ ].join('\n')
99
+
100
+ // Wrap with <CodeSnippets> (if not YAML)
101
+ codeSnippets = isYaml
102
+ ? codeSnippets
103
+ : // Rename/Replace Words via Custom Magic Comments
104
+ processMagicComments(`<CodeSnippets>\n${codeSnippets}\n</CodeSnippets>`)
92
105
 
93
- codeNew += codeSnippets
106
+ // Restore indentation
107
+ codeSnippets = restoreCodeBlockIndent(codeSnippets, codeBlockIndent)
108
+
109
+ // Done
110
+ replacement = codeSnippets
94
111
  }
95
112
 
96
- lastIndex = blockEndIndex
113
+ const blockStartIndex = match.index!
114
+ const blockEndIndex = blockStartIndex + codeBlockOuterStr.length
115
+ magicString.overwrite(blockStartIndex, blockEndIndex, replacement)
97
116
  }
98
- codeNew += code.slice(lastIndex)
99
117
 
100
- return codeNew
118
+ return getMagicStringResult()
101
119
  }
102
120
 
121
+ function wrapCodeSnippet(lang: string, content: string) {
122
+ return `<CodeSnippet codeLang="${lang}">\n${content}\n</CodeSnippet>`
123
+ }
103
124
  function removeCodeBlockIndent(code: string, codeBlockIndent: string, moduleId: string) {
104
125
  if (!codeBlockIndent.length) return code
105
126
  return code
106
127
  .split('\n')
107
128
  .map((line) => {
129
+ const lineStart = codeBlockIndent.trimEnd()
108
130
  assertUsage(
109
- line.startsWith(codeBlockIndent.trimEnd()),
110
- `In ${pc.bold(pc.blue(moduleId))} the line ${pc.bold(line)} must start with ${pc.bold(codeBlockIndent)}`,
131
+ line.startsWith(lineStart),
132
+ `In ${pc.bold(pc.blue(moduleId))} the line '${pc.bold(line)}' must start with '${pc.bold(lineStart)}'`,
111
133
  )
112
134
  return line.slice(codeBlockIndent.length)
113
135
  })
@@ -120,3 +142,18 @@ function restoreCodeBlockIndent(code: string, codeBlockIndent: string) {
120
142
  .map((line) => `${codeBlockIndent}${line}`)
121
143
  .join('\n')
122
144
  }
145
+ function processMagicComments(code: string) {
146
+ // @detype-rename DummyLayout>Layout
147
+ const renameCommentRE = /^\/\/\s@detype-rename\s(\w+)>(\w+)\n/gm
148
+ const matches = Array.from(code.matchAll(renameCommentRE))
149
+
150
+ if (matches.length) {
151
+ for (let i = 0; i < matches.length / 2; i++) {
152
+ const match = matches[i]
153
+ const [fullMatch, renameFrom, renameTo] = match
154
+ code = code.split(fullMatch).join('').replaceAll(renameFrom, renameTo)
155
+ }
156
+ }
157
+
158
+ return code.replaceAll('//~', '')
159
+ }
@@ -13,7 +13,7 @@ function TypescriptOnly({ children }) {
13
13
  }
14
14
  function CodeSnippets({ children }) {
15
15
  const [codeLangSelected, selectCodeLang] = useSelectCodeLang();
16
- return (React.createElement("div", null,
16
+ return (React.createElement("div", { className: "code-snippets" },
17
17
  React.createElement("form", { style: { position: 'relative' } },
18
18
  React.createElement("select", { className: "code-lang-select", onChange: onChange, value: codeLangSelected },
19
19
  React.createElement("option", { value: "js" }, "JavaScript"),
@@ -26,7 +26,7 @@ function CodeSnippets({ children }) {
26
26
  function CodeSnippet({ children, codeLang, tsOnly = false, }) {
27
27
  const [codeLangSelected] = useSelectCodeLang();
28
28
  const displayStyle = tsOnly ? {} : { display: codeLangSelected === codeLang ? 'block' : 'none' };
29
- return (React.createElement("div", { style: { ...displayStyle, position: 'relative' } },
29
+ return (React.createElement("div", { className: "code-snippet", style: { ...displayStyle } },
30
30
  React.createElement(CopyButton, null),
31
31
  children));
32
32
  }
@@ -2,6 +2,7 @@ export { detypePlugin };
2
2
  import module from 'node:module';
3
3
  import { assertUsage } from './utils/assert.js';
4
4
  import pc from '@brillout/picocolors';
5
+ import { getMagicString } from './utils/getMagicString.js';
5
6
  // Cannot use `import { transform } from 'detype'` as it results in errors,
6
7
  // and the package has no default export. Using `module.createRequire` instead.
7
8
  const { transform: detype } = module.createRequire(import.meta.url)('detype');
@@ -29,7 +30,7 @@ const prettierOptions = {
29
30
  // > const hello: string = 'world'
30
31
  // > ```
31
32
  // ~~~
32
- const codeBlockRE = /^(.*)```(tsx?|vue)[^\n]*\n([\s\S]*?)```/gm;
33
+ const codeBlockRE = /^(.*)```(tsx?|vue|yaml)[^\n]*\n([\s\S]*?)```/gm;
33
34
  function detypePlugin() {
34
35
  return {
35
36
  name: '@brillout/docpress:detypePlugin',
@@ -37,8 +38,7 @@ function detypePlugin() {
37
38
  transform: async (code, moduleId) => {
38
39
  if (!moduleId.endsWith('.mdx'))
39
40
  return;
40
- const codeNew = await transformCode(code, moduleId);
41
- return codeNew;
41
+ return await transformCode(code, moduleId);
42
42
  },
43
43
  };
44
44
  }
@@ -46,42 +46,63 @@ async function transformCode(code, moduleId) {
46
46
  const matches = Array.from(code.matchAll(codeBlockRE));
47
47
  if (matches.length === 0)
48
48
  return;
49
- let codeNew = `import { CodeSnippets, CodeSnippet } from '@brillout/docpress';\n\n`;
50
- let lastIndex = 0;
51
- for (const match of matches) {
49
+ const { magicString, getMagicStringResult } = getMagicString(code, moduleId);
50
+ magicString.prepend(`import { CodeSnippets, CodeSnippet } from '@brillout/docpress';\n\n`);
51
+ // [Claude AI] Process matches in reverse order to avoid offset issues
52
+ for (let i = matches.length - 1; i >= 0; i--) {
53
+ const match = matches[i];
52
54
  const [codeBlockOuterStr, codeBlockIndent, codeBlockLang, codeBlockContentWithIndent] = match;
55
+ const isYaml = codeBlockLang === 'yaml';
53
56
  // Remove indentation
54
57
  const codeBlockOpen = codeBlockOuterStr.split('\n')[0].slice(codeBlockIndent.length);
55
58
  const codeBlockContent = removeCodeBlockIndent(codeBlockContentWithIndent, codeBlockIndent, moduleId);
56
- const blockStartIndex = match.index;
57
- const blockEndIndex = blockStartIndex + codeBlockOuterStr.length;
58
- codeNew += code.slice(lastIndex, blockStartIndex);
59
- if (codeBlockOpen.includes('ts-only')) {
60
- codeNew += `${codeBlockIndent}<CodeSnippet codeLang="ts" tsOnly>\n${codeBlockOuterStr}\n${codeBlockIndent}</CodeSnippet>`;
59
+ let replacement;
60
+ if (codeBlockOpen.includes('ts-only') && !isYaml) {
61
+ replacement = `${codeBlockIndent}<CodeSnippet codeLang="ts" tsOnly>\n${codeBlockOuterStr}\n${codeBlockIndent}</CodeSnippet>`;
61
62
  }
62
63
  else {
63
64
  // someFileName.ts => someFileName.js
64
65
  let codeBlockContentJs = codeBlockContent.replaceAll('.ts', '.js');
66
+ const codeBlockClose = '```';
67
+ if (isYaml && codeBlockContentJs === codeBlockContent) {
68
+ continue;
69
+ }
65
70
  // Remove TypeScript
66
- codeBlockContentJs = await detype(codeBlockContentJs, `some-dummy-filename.${codeBlockLang}`, {
67
- removeTsComments: true,
68
- prettierOptions,
69
- });
71
+ if (!isYaml) {
72
+ codeBlockContentJs = await detype(codeBlockContentJs, `some-dummy-filename.${codeBlockLang}`, {
73
+ removeTsComments: true,
74
+ prettierOptions,
75
+ });
76
+ }
77
+ // Update code block open delimiter
70
78
  const codeBlockLangJs = codeBlockLang === 'vue'
71
79
  ? 'vue'
72
80
  : // ts => js | tsx => jsx
73
81
  codeBlockLang.replace('t', 'j');
74
82
  const codeBlockOpenJs = codeBlockOpen.replace(codeBlockLang, codeBlockLangJs);
75
- const codeBlockClose = '```';
76
- const codeSnippetTs = `<CodeSnippet codeLang="ts">\n${codeBlockOpen}\n${codeBlockContent}${codeBlockClose}\n</CodeSnippet>`;
77
- const codeSnippetJs = `<CodeSnippet codeLang="js">\n${codeBlockOpenJs}\n${codeBlockContentJs}${codeBlockClose}\n</CodeSnippet>`;
78
- const codeSnippets = restoreCodeBlockIndent(`<CodeSnippets>\n${codeSnippetJs}\n${codeSnippetTs}\n</CodeSnippets>`, codeBlockIndent);
79
- codeNew += codeSnippets;
83
+ // Wrap each with <CodeSnippet>
84
+ let codeSnippets = [
85
+ wrapCodeSnippet('ts', `${codeBlockOpen}\n${codeBlockContent}${codeBlockClose}`),
86
+ wrapCodeSnippet('js', `${codeBlockOpenJs}\n${codeBlockContentJs}${codeBlockClose}`),
87
+ ].join('\n');
88
+ // Wrap with <CodeSnippets> (if not YAML)
89
+ codeSnippets = isYaml
90
+ ? codeSnippets
91
+ : // Rename/Replace Words via Custom Magic Comments
92
+ processMagicComments(`<CodeSnippets>\n${codeSnippets}\n</CodeSnippets>`);
93
+ // Restore indentation
94
+ codeSnippets = restoreCodeBlockIndent(codeSnippets, codeBlockIndent);
95
+ // Done
96
+ replacement = codeSnippets;
80
97
  }
81
- lastIndex = blockEndIndex;
98
+ const blockStartIndex = match.index;
99
+ const blockEndIndex = blockStartIndex + codeBlockOuterStr.length;
100
+ magicString.overwrite(blockStartIndex, blockEndIndex, replacement);
82
101
  }
83
- codeNew += code.slice(lastIndex);
84
- return codeNew;
102
+ return getMagicStringResult();
103
+ }
104
+ function wrapCodeSnippet(lang, content) {
105
+ return `<CodeSnippet codeLang="${lang}">\n${content}\n</CodeSnippet>`;
85
106
  }
86
107
  function removeCodeBlockIndent(code, codeBlockIndent, moduleId) {
87
108
  if (!codeBlockIndent.length)
@@ -89,7 +110,8 @@ function removeCodeBlockIndent(code, codeBlockIndent, moduleId) {
89
110
  return code
90
111
  .split('\n')
91
112
  .map((line) => {
92
- assertUsage(line.startsWith(codeBlockIndent.trimEnd()), `In ${pc.bold(pc.blue(moduleId))} the line ${pc.bold(line)} must start with ${pc.bold(codeBlockIndent)}`);
113
+ const lineStart = codeBlockIndent.trimEnd();
114
+ assertUsage(line.startsWith(lineStart), `In ${pc.bold(pc.blue(moduleId))} the line '${pc.bold(line)}' must start with '${pc.bold(lineStart)}'`);
93
115
  return line.slice(codeBlockIndent.length);
94
116
  })
95
117
  .join('\n');
@@ -102,3 +124,16 @@ function restoreCodeBlockIndent(code, codeBlockIndent) {
102
124
  .map((line) => `${codeBlockIndent}${line}`)
103
125
  .join('\n');
104
126
  }
127
+ function processMagicComments(code) {
128
+ // @detype-rename DummyLayout>Layout
129
+ const renameCommentRE = /^\/\/\s@detype-rename\s(\w+)>(\w+)\n/gm;
130
+ const matches = Array.from(code.matchAll(renameCommentRE));
131
+ if (matches.length) {
132
+ for (let i = 0; i < matches.length / 2; i++) {
133
+ const match = matches[i];
134
+ const [fullMatch, renameFrom, renameTo] = match;
135
+ code = code.split(fullMatch).join('').replaceAll(renameFrom, renameTo);
136
+ }
137
+ }
138
+ return code.replaceAll('//~', '');
139
+ }
@@ -0,0 +1,9 @@
1
+ export { getMagicString };
2
+ import MagicString from 'magic-string';
3
+ declare function getMagicString(code: string, id: string): {
4
+ magicString: MagicString;
5
+ getMagicStringResult: () => {
6
+ code: string;
7
+ map: import("magic-string").SourceMap;
8
+ };
9
+ };
@@ -0,0 +1,13 @@
1
+ export { getMagicString };
2
+ import MagicString from 'magic-string';
3
+ // Used everywhere instead of `new MagicString()` for consistent source map generation
4
+ function getMagicString(code, id) {
5
+ const magicString = new MagicString(code);
6
+ const getMagicStringResult = () => {
7
+ return {
8
+ code: magicString.toString(),
9
+ map: magicString.generateMap({ hires: true, source: id }),
10
+ };
11
+ };
12
+ return { magicString, getMagicStringResult };
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.15.10-commit-af2d9bc",
3
+ "version": "0.15.10-commit-f89d08f",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -12,6 +12,7 @@
12
12
  "@shikijs/transformers": "1.2.0",
13
13
  "@vitejs/plugin-react-swc": "^3.10.2",
14
14
  "detype": "^1.1.2",
15
+ "magic-string": "^0.30.18",
15
16
  "rehype-pretty-code": "0.13.0",
16
17
  "remark-gfm": "4.0.0",
17
18
  "shiki": "1.2.0",
@@ -0,0 +1,17 @@
1
+ export { getMagicString }
2
+
3
+ import MagicString from 'magic-string'
4
+
5
+ // Used everywhere instead of `new MagicString()` for consistent source map generation
6
+ function getMagicString(code: string, id: string) {
7
+ const magicString = new MagicString(code)
8
+
9
+ const getMagicStringResult = () => {
10
+ return {
11
+ code: magicString.toString(),
12
+ map: magicString.generateMap({ hires: true, source: id }),
13
+ }
14
+ }
15
+
16
+ return { magicString, getMagicStringResult }
17
+ }