@mistweaverco/mdsvex-shiki 1.0.17 → 1.1.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.
package/README.md CHANGED
@@ -9,7 +9,8 @@ limited in features and customization options.
9
9
  We needed a better solution for our SvelteKit projects,
10
10
  so we created this package to fill the gap.
11
11
 
12
- <img width="945" height="874" alt="Screenshot" src="https://github.com/user-attachments/assets/c717f559-ae96-4372-a1ac-3eba3d4db340" />
12
+ <img width="794" height="558" alt="Screenshot" src="https://github.com/user-attachments/assets/38250632-432a-4f2a-8722-0ef1e4108f38" />
13
+
13
14
 
14
15
  ## Installation
15
16
 
@@ -17,27 +18,31 @@ Using your package manager of choice, run:
17
18
 
18
19
  ```bash
19
20
  # npm
20
- npm install @mistweaverco/mdsvex-shiki@v1.0.16
21
+ npm install @mistweaverco/mdsvex-shiki@v1.1.1
21
22
 
22
23
  # yarn
23
- yarn add @mistweaverco/mdsvex-shiki@v1.0.
24
+ yarn add @mistweaverco/mdsvex-shiki@v1.1.1
24
25
 
25
26
  # bun
26
- bun add @mistweaverco/mdsvex-shiki@v1.0.16
27
+ bun add @mistweaverco/mdsvex-shiki@v1.1.1
27
28
 
28
29
  # pnpm
29
- pnpm add @mistweaverco/mdsvex-shiki@v1.0.16
30
+ pnpm add @mistweaverco/mdsvex-shiki@v1.1.1
30
31
 
31
32
  # deno
32
- deno add npm:@mistweaverco/mdsvex-shiki@v1.0.16
33
+ deno add npm:@mistweaverco/mdsvex-shiki@v1.1.1
33
34
  ```
34
35
 
35
36
  ## Configuration
36
37
 
37
38
  Available options are as follows:
38
39
 
39
- - `displayTitle`: Whether to show the title bar when a title is
40
- provided in the code block info string. (default: `false`)
40
+ - `displayPath`: Whether to show the file path in the title bar when
41
+ a path is provided. (default: `false`)
42
+ (e.g., `sh path=/path/to/file.sh`).
43
+ Paths are displayed with directory and file icons.
44
+ Long paths are collapsed
45
+ with a tooltip showing the full path.
41
46
  - `displayLang`: Whether to show the language label in
42
47
  the title bar. (default: `false`)
43
48
  - `disableCopyButton`: Whether to disable the copy button.
@@ -55,7 +60,7 @@ Available options are as follows:
55
60
 
56
61
  ```js
57
62
  const options = {
58
- displayTitle: true,
63
+ displayPath: true,
59
64
  displayLang: true,
60
65
  shikiOptions: {
61
66
  theme: 'nord',
@@ -68,7 +73,7 @@ const options = {
68
73
  If you just want to basic Shiki styling without the bar,
69
74
  you don't need to import this CSS file.
70
75
 
71
- If you to display the title, language and/or copy button,
76
+ If you want to display the path, language and/or copy button,
72
77
  you **must** import the CSS file.
73
78
 
74
79
  Probably in the root layout or HTML file that wraps your markdown content.
@@ -126,7 +131,7 @@ const config = {
126
131
  // ...
127
132
  highlight: {
128
133
  highlighter: await mdsvexShiki({
129
- displayTitle: true,
134
+ displayPath: true,
130
135
  displayLang: true,
131
136
  shikiOptions: {
132
137
  // Shiki options
@@ -4713,9 +4713,53 @@ function extractText(node) {
4713
4713
  }
4714
4714
  return "";
4715
4715
  }
4716
+ function parseMetaString(metaString) {
4717
+ if (!metaString) {
4718
+ return {
4719
+ path: undefined
4720
+ };
4721
+ }
4722
+ const pathMatch = metaString.match(/(?:^|\s)path=(?:"([^"]+)"|([^\s]+))/);
4723
+ const pathValue = pathMatch?.[1] ?? pathMatch?.[2];
4724
+ return {
4725
+ path: pathValue
4726
+ };
4727
+ }
4728
+ function formatPathSegments(path) {
4729
+ const normalizedPath = path.replace(/\\/g, "/");
4730
+ const segments = normalizedPath.split("/").filter((s) => s.length > 0);
4731
+ if (segments.length === 0) {
4732
+ return [];
4733
+ }
4734
+ if (segments.length === 1) {
4735
+ return [{ type: "file", value: segments[0] ?? "" }];
4736
+ }
4737
+ const result = [];
4738
+ const directories = segments.slice(0, -1);
4739
+ const file = segments[segments.length - 1];
4740
+ if (directories.length > 2) {
4741
+ result.push({ type: "directory", value: directories[0] ?? "" });
4742
+ result.push({ type: "directory", value: ".." });
4743
+ result.push({
4744
+ type: "directory",
4745
+ value: directories[directories.length - 1] ?? ""
4746
+ });
4747
+ } else {
4748
+ directories.forEach((dir) => {
4749
+ result.push({ type: "directory", value: dir ?? "" });
4750
+ });
4751
+ }
4752
+ result.push({ type: "file", value: file ?? "" });
4753
+ return result;
4754
+ }
4716
4755
 
4717
4756
  // src/transformers.ts
4718
- var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4757
+ var mdsvexWrapItUpTransformer = (lang, codeText, meta, options = {}) => {
4758
+ const {
4759
+ disableCopyButton = false,
4760
+ displayPath = true,
4761
+ displayLang = true
4762
+ } = options;
4719
4763
  return {
4720
4764
  name: "transformerMdsvexWrapItUp",
4721
4765
  enforce: "post",
@@ -4733,11 +4777,86 @@ var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4733
4777
  }
4734
4778
  });
4735
4779
  }
4736
- const headerChildren = [
4737
- {
4780
+ const headerChildren = [];
4781
+ const { path } = parseMetaString(meta);
4782
+ if (displayPath && path) {
4783
+ const segments = formatPathSegments(path);
4784
+ const allSegments = path.split(/[/\\]/).filter((s) => s.length > 0);
4785
+ const hasCollapsedSegments = allSegments.length > 3;
4786
+ const fullPath = allSegments.join("/");
4787
+ const pathChildren = [];
4788
+ segments.forEach((segment, index) => {
4789
+ const isDirectory = segment.type === "directory";
4790
+ const isCollapsed = segment.value === "..";
4791
+ const dirIconPath = "M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z";
4792
+ const fileIconPath = "M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-8.8 0-16 7.2-16 16s7.2 16 16 16H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zM384 128H256V0L384 128z";
4793
+ const properties = {
4794
+ class: `path-segment ${isDirectory ? "directory" : "file"} ${isCollapsed ? "collapsed" : ""}`
4795
+ };
4796
+ if (isCollapsed && hasCollapsedSegments) {
4797
+ properties.title = fullPath;
4798
+ }
4799
+ pathChildren.push({
4800
+ type: "element",
4801
+ tagName: "span",
4802
+ properties,
4803
+ children: [
4804
+ {
4805
+ type: "element",
4806
+ tagName: "svg",
4807
+ properties: {
4808
+ class: "icon",
4809
+ width: "14",
4810
+ height: "14",
4811
+ viewBox: "0 0 512 512",
4812
+ fill: "currentColor",
4813
+ "aria-hidden": "true"
4814
+ },
4815
+ children: [
4816
+ {
4817
+ type: "element",
4818
+ tagName: "path",
4819
+ properties: {
4820
+ d: isDirectory ? dirIconPath : fileIconPath
4821
+ },
4822
+ children: []
4823
+ }
4824
+ ]
4825
+ },
4826
+ {
4827
+ type: "text",
4828
+ value: segment.value
4829
+ }
4830
+ ]
4831
+ });
4832
+ if (index < segments.length - 1) {
4833
+ pathChildren.push({
4834
+ type: "element",
4835
+ tagName: "span",
4836
+ properties: { class: "path-separator" },
4837
+ children: [
4838
+ {
4839
+ type: "text",
4840
+ value: "/"
4841
+ }
4842
+ ]
4843
+ });
4844
+ }
4845
+ });
4846
+ headerChildren.push({
4847
+ type: "element",
4848
+ tagName: "span",
4849
+ properties: { class: "path" },
4850
+ children: pathChildren
4851
+ });
4852
+ }
4853
+ if (displayLang) {
4854
+ headerChildren.push({
4738
4855
  type: "element",
4739
4856
  tagName: "span",
4740
- properties: { class: "language" },
4857
+ properties: {
4858
+ class: "language"
4859
+ },
4741
4860
  children: [
4742
4861
  {
4743
4862
  type: "element",
@@ -4766,8 +4885,8 @@ var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4766
4885
  value: lang
4767
4886
  }
4768
4887
  ]
4769
- }
4770
- ];
4888
+ });
4889
+ }
4771
4890
  if (!disableCopyButton) {
4772
4891
  headerChildren.push({
4773
4892
  type: "element",
@@ -13814,7 +13933,7 @@ var mdsvexShiki = async (config) => {
13814
13933
  delete shikiOptions.themes;
13815
13934
  delete shikiOptions.langs;
13816
13935
  await getHighlighterInstance(themes, langs);
13817
- return async (code, lang245) => {
13936
+ return async (code, lang245, meta) => {
13818
13937
  lang245 = lang245 ?? "text";
13819
13938
  const highlighter = await getHighlighterInstance(themes, langs);
13820
13939
  const transformers = [
@@ -13822,7 +13941,7 @@ var mdsvexShiki = async (config) => {
13822
13941
  ...shikiOptions.transformers || []
13823
13942
  ];
13824
13943
  if (!transformers.find((t) => t.name === "transformerMdsvexWrapItUp")) {
13825
- transformers.push(mdsvexWrapItUpTransformer(lang245, code, config.disableCopyButton));
13944
+ transformers.push(mdsvexWrapItUpTransformer(lang245, code, meta, config));
13826
13945
  }
13827
13946
  const html5 = highlighter.codeToHtml(code, {
13828
13947
  ...shikiOptions,
@@ -16,7 +16,7 @@ import { Action } from 'svelte/action';
16
16
  */
17
17
  export declare const copyAction: Action<HTMLElement>;
18
18
  export type HighlighterOptions = {
19
- displayTitle?: boolean;
19
+ displayPath?: boolean;
20
20
  displayLanguage?: boolean;
21
21
  disableCopyButton?: boolean;
22
22
  shikiOptions?: Partial<CodeToHastOptions<BundledLanguage, BundledTheme>> & {
@@ -28,7 +28,7 @@ export type HighlighterOptions = {
28
28
  };
29
29
  };
30
30
  export declare const defaultShikiOptions: Partial<CodeToHastOptions<BundledLanguage, BundledTheme>>;
31
- export declare const mdsvexShiki: (config: HighlighterOptions) => Promise<(code: string, lang: string) => Promise<string>>;
31
+ export declare const mdsvexShiki: (config: HighlighterOptions) => Promise<(code: string, lang: string, meta?: string) => Promise<string>>;
32
32
 
33
33
  export {
34
34
  mdsvexShiki as default,
@@ -4686,9 +4686,53 @@ function extractText(node) {
4686
4686
  }
4687
4687
  return "";
4688
4688
  }
4689
+ function parseMetaString(metaString) {
4690
+ if (!metaString) {
4691
+ return {
4692
+ path: undefined
4693
+ };
4694
+ }
4695
+ const pathMatch = metaString.match(/(?:^|\s)path=(?:"([^"]+)"|([^\s]+))/);
4696
+ const pathValue = pathMatch?.[1] ?? pathMatch?.[2];
4697
+ return {
4698
+ path: pathValue
4699
+ };
4700
+ }
4701
+ function formatPathSegments(path) {
4702
+ const normalizedPath = path.replace(/\\/g, "/");
4703
+ const segments = normalizedPath.split("/").filter((s) => s.length > 0);
4704
+ if (segments.length === 0) {
4705
+ return [];
4706
+ }
4707
+ if (segments.length === 1) {
4708
+ return [{ type: "file", value: segments[0] ?? "" }];
4709
+ }
4710
+ const result = [];
4711
+ const directories = segments.slice(0, -1);
4712
+ const file = segments[segments.length - 1];
4713
+ if (directories.length > 2) {
4714
+ result.push({ type: "directory", value: directories[0] ?? "" });
4715
+ result.push({ type: "directory", value: ".." });
4716
+ result.push({
4717
+ type: "directory",
4718
+ value: directories[directories.length - 1] ?? ""
4719
+ });
4720
+ } else {
4721
+ directories.forEach((dir) => {
4722
+ result.push({ type: "directory", value: dir ?? "" });
4723
+ });
4724
+ }
4725
+ result.push({ type: "file", value: file ?? "" });
4726
+ return result;
4727
+ }
4689
4728
 
4690
4729
  // src/transformers.ts
4691
- var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4730
+ var mdsvexWrapItUpTransformer = (lang, codeText, meta, options = {}) => {
4731
+ const {
4732
+ disableCopyButton = false,
4733
+ displayPath = true,
4734
+ displayLang = true
4735
+ } = options;
4692
4736
  return {
4693
4737
  name: "transformerMdsvexWrapItUp",
4694
4738
  enforce: "post",
@@ -4706,11 +4750,86 @@ var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4706
4750
  }
4707
4751
  });
4708
4752
  }
4709
- const headerChildren = [
4710
- {
4753
+ const headerChildren = [];
4754
+ const { path } = parseMetaString(meta);
4755
+ if (displayPath && path) {
4756
+ const segments = formatPathSegments(path);
4757
+ const allSegments = path.split(/[/\\]/).filter((s) => s.length > 0);
4758
+ const hasCollapsedSegments = allSegments.length > 3;
4759
+ const fullPath = allSegments.join("/");
4760
+ const pathChildren = [];
4761
+ segments.forEach((segment, index) => {
4762
+ const isDirectory = segment.type === "directory";
4763
+ const isCollapsed = segment.value === "..";
4764
+ const dirIconPath = "M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z";
4765
+ const fileIconPath = "M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-8.8 0-16 7.2-16 16s7.2 16 16 16H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zM384 128H256V0L384 128z";
4766
+ const properties = {
4767
+ class: `path-segment ${isDirectory ? "directory" : "file"} ${isCollapsed ? "collapsed" : ""}`
4768
+ };
4769
+ if (isCollapsed && hasCollapsedSegments) {
4770
+ properties.title = fullPath;
4771
+ }
4772
+ pathChildren.push({
4773
+ type: "element",
4774
+ tagName: "span",
4775
+ properties,
4776
+ children: [
4777
+ {
4778
+ type: "element",
4779
+ tagName: "svg",
4780
+ properties: {
4781
+ class: "icon",
4782
+ width: "14",
4783
+ height: "14",
4784
+ viewBox: "0 0 512 512",
4785
+ fill: "currentColor",
4786
+ "aria-hidden": "true"
4787
+ },
4788
+ children: [
4789
+ {
4790
+ type: "element",
4791
+ tagName: "path",
4792
+ properties: {
4793
+ d: isDirectory ? dirIconPath : fileIconPath
4794
+ },
4795
+ children: []
4796
+ }
4797
+ ]
4798
+ },
4799
+ {
4800
+ type: "text",
4801
+ value: segment.value
4802
+ }
4803
+ ]
4804
+ });
4805
+ if (index < segments.length - 1) {
4806
+ pathChildren.push({
4807
+ type: "element",
4808
+ tagName: "span",
4809
+ properties: { class: "path-separator" },
4810
+ children: [
4811
+ {
4812
+ type: "text",
4813
+ value: "/"
4814
+ }
4815
+ ]
4816
+ });
4817
+ }
4818
+ });
4819
+ headerChildren.push({
4820
+ type: "element",
4821
+ tagName: "span",
4822
+ properties: { class: "path" },
4823
+ children: pathChildren
4824
+ });
4825
+ }
4826
+ if (displayLang) {
4827
+ headerChildren.push({
4711
4828
  type: "element",
4712
4829
  tagName: "span",
4713
- properties: { class: "language" },
4830
+ properties: {
4831
+ class: "language"
4832
+ },
4714
4833
  children: [
4715
4834
  {
4716
4835
  type: "element",
@@ -4739,8 +4858,8 @@ var mdsvexWrapItUpTransformer = (lang, codeText, disableCopyButton) => {
4739
4858
  value: lang
4740
4859
  }
4741
4860
  ]
4742
- }
4743
- ];
4861
+ });
4862
+ }
4744
4863
  if (!disableCopyButton) {
4745
4864
  headerChildren.push({
4746
4865
  type: "element",
@@ -13787,7 +13906,7 @@ var mdsvexShiki = async (config) => {
13787
13906
  delete shikiOptions.themes;
13788
13907
  delete shikiOptions.langs;
13789
13908
  await getHighlighterInstance(themes, langs);
13790
- return async (code, lang245) => {
13909
+ return async (code, lang245, meta) => {
13791
13910
  lang245 = lang245 ?? "text";
13792
13911
  const highlighter = await getHighlighterInstance(themes, langs);
13793
13912
  const transformers = [
@@ -13795,7 +13914,7 @@ var mdsvexShiki = async (config) => {
13795
13914
  ...shikiOptions.transformers || []
13796
13915
  ];
13797
13916
  if (!transformers.find((t) => t.name === "transformerMdsvexWrapItUp")) {
13798
- transformers.push(mdsvexWrapItUpTransformer(lang245, code, config.disableCopyButton));
13917
+ transformers.push(mdsvexWrapItUpTransformer(lang245, code, meta, config));
13799
13918
  }
13800
13919
  const html5 = highlighter.codeToHtml(code, {
13801
13920
  ...shikiOptions,
package/package.json CHANGED
@@ -1,23 +1,21 @@
1
1
  {
2
2
  "name": "@mistweaverco/mdsvex-shiki",
3
- "version": "1.0.17",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
7
7
  "types": "./index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./index.d.ts",
11
- "import": "./index.js",
12
- "require": "./index.cjs"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
13
  },
14
14
  "./styles.css": "./styles.css"
15
15
  },
16
16
  "files": [
17
- "styles.css",
18
- "*.js",
19
- "*.cjs",
20
- "*.d.ts"
17
+ "dist/*.{js,cjs,d.ts,css}",
18
+ "styles.css"
21
19
  ],
22
20
  "scripts": {
23
21
  "test": "echo \"Error: no test specified\" && exit 1",
package/styles.css CHANGED
@@ -38,32 +38,27 @@
38
38
  }
39
39
 
40
40
  .mdsvex-shiki .header {
41
- position: absolute;
42
- top: 0;
43
- right: 0;
44
- z-index: 10;
45
41
  background-color: transparent;
46
- padding: 0.5em 1em;
47
42
  pointer-events: none;
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 0.5em;
48
46
  }
49
47
 
50
48
  .mdsvex-shiki .header > * {
51
49
  pointer-events: auto;
52
- position: absolute;
53
- top: 0;
54
- right: 0;
50
+ position: relative;
55
51
  transition: opacity 0.2s ease;
56
52
  }
57
53
 
58
54
  .mdsvex-shiki .header .language {
59
- right: 12px;
60
- top: 8px;
61
55
  color: var(--shiki-dark);
62
56
  font-size: 0.875em;
63
57
  opacity: 0.7;
64
58
  display: flex;
65
59
  align-items: center;
66
60
  gap: 0.5em;
61
+ margin-left: auto;
67
62
  }
68
63
 
69
64
  .mdsvex-shiki .header .language .icon {
@@ -72,29 +67,17 @@
72
67
  flex-shrink: 0;
73
68
  }
74
69
 
75
- .mdsvex-shiki:hover .header .language,
76
- .mdsvex-shiki:focus-within .header .language {
77
- opacity: 0;
78
- }
79
-
80
70
  .mdsvex-shiki .header .copy {
81
71
  color: var(--shiki-dark);
82
72
  background-color: transparent;
83
73
  border: none;
84
- right: 6px;
85
- top: 8px;
86
74
  cursor: pointer;
87
- opacity: 0;
75
+ opacity: 1;
88
76
  display: flex;
89
77
  align-items: center;
90
78
  justify-content: center;
91
79
  }
92
80
 
93
- .mdsvex-shiki:hover .header .copy,
94
- .mdsvex-shiki:focus-within .header .copy {
95
- opacity: 1;
96
- }
97
-
98
81
  .mdsvex-shiki .header .copy .icon {
99
82
  width: 16px;
100
83
  height: 16px;
@@ -121,3 +104,43 @@
121
104
  color: var(--shiki-dark-accent-active);
122
105
  }
123
106
 
107
+ .mdsvex-shiki .header .path {
108
+ color: var(--shiki-dark);
109
+ font-size: 0.875em;
110
+ opacity: 0.7;
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.25em;
114
+ max-width: calc(100% - 100px);
115
+ overflow-y: hidden;
116
+ overflow-x: auto;
117
+ white-space: nowrap;
118
+ }
119
+
120
+ .mdsvex-shiki .header .path .icon {
121
+ width: 14px;
122
+ height: 14px;
123
+ flex-shrink: 0;
124
+ }
125
+
126
+ .mdsvex-shiki .header .path .path-segment {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: 0.25em;
130
+ }
131
+
132
+ .mdsvex-shiki .header .path .path-separator {
133
+ color: var(--shiki-dark);
134
+ opacity: 0.5;
135
+ margin: 0 0.125em;
136
+ }
137
+
138
+ .mdsvex-shiki .header .path .path-segment.collapsed {
139
+ cursor: help;
140
+ }
141
+
142
+ .mdsvex-shiki:hover .header .path,
143
+ .mdsvex-shiki:focus-within .header .path {
144
+ opacity: 1;
145
+ }
146
+