@tnotesjs/ui 0.3.0 → 0.3.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.
@@ -132,13 +132,17 @@ async function prepareCodeHighlighter(languages = []) {
132
132
  );
133
133
  return highlighter;
134
134
  }
135
- function transformers(meta) {
135
+ function lineNumberDigits(source, meta) {
136
+ return String(meta.startLine + source.split("\n").length - 1).length;
137
+ }
138
+ function transformers(meta, gutterDigits) {
136
139
  return [
137
140
  {
138
141
  name: "tnotes-code-meta",
139
142
  pre(node) {
140
143
  this.addClassToHast(node, "tn-code-highlight");
141
144
  node.properties.tabindex = 0;
145
+ node.properties.style = `--tn-line-digits: ${gutterDigits}`;
142
146
  },
143
147
  line(node, line) {
144
148
  node.properties["data-line"] = String(meta.startLine + line - 1);
@@ -150,13 +154,14 @@ function transformers(meta) {
150
154
  function highlightCodeSync(code, info = "") {
151
155
  if (!ready) throw new Error("Call prepareCodeHighlighter before rendering Markdown");
152
156
  const meta = parseCodeMeta(info);
157
+ const source = code.replace(/\n$/, "");
153
158
  const normalizedLanguage = normalizeCodeLanguage(meta.language);
154
159
  const language = ready.getLoadedLanguages().includes(normalizedLanguage) ? normalizedLanguage : "text";
155
- return ready.codeToHtml(code.replace(/\n$/, ""), {
160
+ return ready.codeToHtml(source, {
156
161
  lang: language,
157
162
  themes: { light: "github-light", dark: "github-dark" },
158
163
  defaultColor: false,
159
- transformers: transformers(meta)
164
+ transformers: transformers(meta, lineNumberDigits(source, meta))
160
165
  });
161
166
  }
162
167
  async function highlightCode(code, info = "") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tnotesjs/ui",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Shared Vue UI for TNotes built-in blocks — consumed by the TNotes SSG and Desk.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -82,13 +82,13 @@
82
82
  },
83
83
  "dependencies": {
84
84
  "@excalidraw/excalidraw": "0.18.1",
85
+ "@tnotesjs/mindmap-core": "workspace:^",
85
86
  "marked": "^15.0.11",
86
87
  "mermaid": "^11.17.0",
87
88
  "react": "18.3.1",
88
89
  "react-dom": "18.3.1",
89
90
  "sass": "^1.90.0",
90
- "shiki": "4.4.2",
91
- "@tnotesjs/mindmap-core": "^0.2.2"
91
+ "shiki": "4.4.2"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@eslint/js": "^10.0.1",
@@ -134,6 +134,7 @@
134
134
  "typecheck": "tsc --noEmit -p tsconfig.json",
135
135
  "test": "vitest run",
136
136
  "lint": "eslint . --max-warnings=0",
137
- "lint:fix": "eslint . --fix"
137
+ "lint:fix": "eslint . --fix",
138
+ "prepublishOnly": "pnpm test && pnpm typecheck && pnpm build"
138
139
  }
139
- }
140
+ }
@@ -127,7 +127,18 @@ export async function prepareCodeHighlighter(languages: string[] = []): Promise<
127
127
  return highlighter
128
128
  }
129
129
 
130
- function transformers(meta: CodeMeta): ShikiTransformer[] {
130
+ /**
131
+ * How many characters the line-number gutter has to hold.
132
+ *
133
+ * Counted from the source up front because the width belongs on the root `<pre>`
134
+ * and has to be set before Shiki visits the lines: sizing each `::before` from
135
+ * its own number would let the code text jump between lines.
136
+ */
137
+ function lineNumberDigits(source: string, meta: CodeMeta): number {
138
+ return String(meta.startLine + source.split('\n').length - 1).length
139
+ }
140
+
141
+ function transformers(meta: CodeMeta, gutterDigits: number): ShikiTransformer[] {
131
142
  // Fence `{N}` line highlights only — match Desk CodeMirror. No Shiki
132
143
  // notation transformers (`[!code ++]` etc.); Desk has no write path for them.
133
144
  return [
@@ -136,6 +147,8 @@ function transformers(meta: CodeMeta): ShikiTransformer[] {
136
147
  pre(node) {
137
148
  this.addClassToHast(node, 'tn-code-highlight')
138
149
  node.properties.tabindex = 0
150
+ // Inherited by every line's `::before`, which is what sizes the gutter.
151
+ node.properties.style = `--tn-line-digits: ${gutterDigits}`
139
152
  },
140
153
  line(node, line) {
141
154
  node.properties['data-line'] = String(meta.startLine + line - 1)
@@ -149,15 +162,16 @@ function transformers(meta: CodeMeta): ShikiTransformer[] {
149
162
  export function highlightCodeSync(code: string, info = ''): string {
150
163
  if (!ready) throw new Error('Call prepareCodeHighlighter before rendering Markdown')
151
164
  const meta = parseCodeMeta(info)
165
+ const source = code.replace(/\n$/, '')
152
166
  const normalizedLanguage = normalizeCodeLanguage(meta.language)
153
167
  const language = ready.getLoadedLanguages().includes(normalizedLanguage)
154
168
  ? normalizedLanguage
155
169
  : 'text'
156
- return ready.codeToHtml(code.replace(/\n$/, ''), {
170
+ return ready.codeToHtml(source, {
157
171
  lang: language,
158
172
  themes: { light: 'github-light', dark: 'github-dark' },
159
173
  defaultColor: false,
160
- transformers: transformers(meta)
174
+ transformers: transformers(meta, lineNumberDigits(source, meta))
161
175
  })
162
176
  }
163
177
 
@@ -116,8 +116,14 @@
116
116
  .tn-code-block.has-line-numbers .line::before {
117
117
  content: attr(data-line);
118
118
  display: inline-block;
119
- width: 3ch;
120
- padding-right: 16px;
119
+ /*
120
+ * Gutter width follows the widest number this block actually prints — the
121
+ * highlighter sets `--tn-line-digits` per block, so a two-line snippet is not
122
+ * padded like a thousand-line file. 3ch is the fallback for renderers that
123
+ * bring their own line numbers.
124
+ */
125
+ width: calc(var(--tn-line-digits, 3) * 1ch);
126
+ padding-right: 12px;
121
127
  text-align: right;
122
128
  color: var(--tn-c-text-2);
123
129
  user-select: none;