diff --git a/.changeset/rare-gifts-sip.md b/.changeset/rare-gifts-sip.md
new file mode 100644
index 0000000..8f5ab16
--- /dev/null
+++ b/.changeset/rare-gifts-sip.md
@@ -0,0 +1,9 @@
+---
+"react-github-permalink": minor
+---
+
+Switch style provider from HLJS to Prism. (Prism supports TSX).
+
+Auto detect language based on file extension.
+
+Adds a `language` prop to override any auto-detection behaviour.
diff --git a/.env.template b/.env.template
new file mode 100644
index 0000000..b4044eb
--- /dev/null
+++ b/.env.template
@@ -0,0 +1 @@
+STORYBOOK_GITHUB_TOKEN=${GITHUB_TOKEN_READONLY}
diff --git a/.gitignore b/.gitignore
index ee8face..ccbe188 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
.pnp.js
.yarn/install-state.gz
+.env
+
# testing
/coverage
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
deleted file mode 100644
index f9f6cbe..0000000
--- a/.storybook/preview.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Preview } from "@storybook/react";
-import './global.css';
-
-const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
- },
-};
-
-export default preview;
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
new file mode 100644
index 0000000..edde195
--- /dev/null
+++ b/.storybook/preview.tsx
@@ -0,0 +1,22 @@
+import type { Preview } from "@storybook/react";
+import './global.css';
+import { GithubPermalinkProvider } from "../src/library/config/GithubPermalinkContext";
+
+const preview: Preview = {
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ },
+ decorators: [(Story) => (
+
+
+
+
+
)]
+};
+
+export default preview;
diff --git a/README.md b/README.md
index fb1b2b5..5fcbec7 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,15 @@ I highly rate the [`vscode-copy-github-permalink` plugin](https://marketplace.vi
https://codesandbox.io/s/exciting-nova-js5zlk?file=/src/App.js
+## Language Support
+
+Langauge is naively auto detected based on file extension. See [logic here for all auto-detected languages](https://github.com/dwjohnston/react-github-permalink/pull/73/files#diff-b6feb43e40d6eae1cba733450d691be8f83a1a50ecbff1b890cd343b2039ece1).
+
+If this does not suit you, you can override the autodetected langauage with the `language` prop.
+
+
+
+
## RSC Compatibility / Three modes of operation
This package is compatible with Next 13+ and the components can be used as RSCs if you wish.
diff --git a/sample_files/sample.dockerfile b/sample_files/sample.dockerfile
new file mode 100644
index 0000000..b95a456
--- /dev/null
+++ b/sample_files/sample.dockerfile
@@ -0,0 +1,20 @@
+# Use an official Node.js runtime as a parent image
+FROM node:18-alpine
+
+# Set the working directory
+WORKDIR /app
+
+# Copy package.json and package-lock.json
+COPY package*.json ./
+
+# Install dependencies
+RUN npm install
+
+# Copy the rest of the application code
+COPY . .
+
+# Expose the app port
+EXPOSE 3000
+
+# Start the application
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/sample_files/sample.html b/sample_files/sample.html
new file mode 100644
index 0000000..9230168
--- /dev/null
+++ b/sample_files/sample.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Sample HTML Page
+
+
+
+ Welcome to the Sample HTML Page
+ This is a simple HTML file for demonstration purposes.
+
+ - First item
+ - Second item
+ - Third item
+
+
+
+
\ No newline at end of file
diff --git a/sample_files/sample.xml b/sample_files/sample.xml
new file mode 100644
index 0000000..674eac8
--- /dev/null
+++ b/sample_files/sample.xml
@@ -0,0 +1,15 @@
+
+
+
+ Introduction to XML
+ Jane Doe
+ 2022
+ Technology
+
+
+ Learning React
+ John Smith
+ 2023
+ Programming
+
+
\ No newline at end of file
diff --git a/sample_files/sample1.tsx b/sample_files/sample1.tsx
new file mode 100644
index 0000000..5a79915
--- /dev/null
+++ b/sample_files/sample1.tsx
@@ -0,0 +1,11 @@
+function Foo(props: { name: string }) {
+ return {props.name}
+
+}
+
+export function Demo() {
+ return
+
+ Hello world!
+
+}
\ No newline at end of file
diff --git a/src/library/GithubPermalink/GithubPermalink.stories.tsx b/src/library/GithubPermalink/GithubPermalink.stories.tsx
index e2a07fe..f2820aa 100644
--- a/src/library/GithubPermalink/GithubPermalink.stories.tsx
+++ b/src/library/GithubPermalink/GithubPermalink.stories.tsx
@@ -23,6 +23,33 @@ export const Primary: Story = {
),
};
+export const DifferentLanguages: Story = {
+ render: () => (
+
Go
+
+
+
JavaScript
+
+
+
SQL
+
+
+
TSX
+
+
+
Docker file
+
+
+
XML
+
+
+
HTML
+
+
+
+ ),
+};
+
export const WithBackground: Story = {
render: () => (
diff --git a/src/library/GithubPermalink/GithubPermalink.tsx b/src/library/GithubPermalink/GithubPermalink.tsx
index 2a7c5b2..351d46b 100644
--- a/src/library/GithubPermalink/GithubPermalink.tsx
+++ b/src/library/GithubPermalink/GithubPermalink.tsx
@@ -1,11 +1,10 @@
"use client"
-import {useContext, useEffect, useState } from "react";
+import { useContext, useEffect, useState } from "react";
import { GithubPermalinkDataResponse, GithubPermalinkContext } from "../config/GithubPermalinkContext";
-import { useMediaQuery } from "react-responsive";
import { GithubPermalinkBase, GithubPermalinkBaseProps } from "./GithubPermalinkBase";
-type GithubPermalinkProps = Omit & {permalink: string};
+type GithubPermalinkProps = Omit & { permalink: string };
export function GithubPermalink(props: GithubPermalinkProps) {
const { permalink } = props;
@@ -28,7 +27,7 @@ export function GithubPermalink(props: GithubPermalinkProps) {
}
- return
+ return
}
diff --git a/src/library/GithubPermalink/GithubPermalinkBase.tsx b/src/library/GithubPermalink/GithubPermalinkBase.tsx
index 2529e0a..7036ebc 100644
--- a/src/library/GithubPermalink/GithubPermalinkBase.tsx
+++ b/src/library/GithubPermalink/GithubPermalinkBase.tsx
@@ -4,8 +4,9 @@ import { GithubSvg } from "../GithubSvg/GithubSvg";
import { PropsWithChildren } from "react";
import { SyntaxHighlight } from "../SyntaxHighlight/SyntaxHighlight";
import { formatForLineExclusions } from "./formatLineExclusions";
-import { CopySvg } from "../images/CopySvg";
import { CopyButton } from "../common/CopyButton/CopyButton";
+import { getLanguageFromPath } from "../utils/getLanguageFromPath";
+import { AvailableLanguagesPrism } from "../SyntaxHighlight/availableLanguagesPrism";
export type GithubPermalinkBaseProps = {
className?: string;
@@ -13,19 +14,21 @@ export type GithubPermalinkBaseProps = {
excludeLines?: Array<[from: number, to: number]>;
excludeText?: string;
data: GithubPermalinkDataResponse;
+ language?: AvailableLanguagesPrism;
}
export function GithubPermalinkBase(props: GithubPermalinkBaseProps) {
- const { data, permalink, excludeLines, excludeText = "" } = props;
+ const { data, permalink, excludeLines, excludeText = "", } = props;
if (data.status === "ok") {
const formatedLineExclusions = formatForLineExclusions(data, excludeLines);
+ const language = props.language ?? getLanguageFromPath(data.path);
const clipboard = formatedLineExclusions.reduce((acc, cur) => {
if (cur.isExclude) {
@@ -41,11 +44,11 @@ export function GithubPermalinkBase(props: GithubPermalinkBaseProps) {
{formatedLineExclusions.map((v) => {
if (v.isExclude) {
- return
+ return
}
- return
+ return
})}
diff --git a/src/library/GithubPermalink/github-permalink.css b/src/library/GithubPermalink/github-permalink.css
index 5229982..0e6e0ec 100644
--- a/src/library/GithubPermalink/github-permalink.css
+++ b/src/library/GithubPermalink/github-permalink.css
@@ -1,25 +1,23 @@
-
-
.rgp-base {
- --rgp-color-bg-stark: white;
+ --rgp-color-bg-stark: white;
--rgp-color-text-stark: rgb(31, 35, 40);
-
+
--rgp-color-bg-frame: rgb(246, 248, 250);
--rgp-color-text-frame: rgb(101, 109, 118);
-
- --rgp-color-border: rgb(208, 215, 222);
+
+ --rgp-color-border: rgb(208, 215, 222);
--rgp-color-issuenumber: rgb(101, 109, 118);
- --rgp-color-file-link: rgb(9, 105, 218);
+ --rgp-color-file-link: rgb(9, 105, 218);
--rgp-color-commit-link: rgb(31, 35, 40);
- --rgp-color-status-foreground: white;
+ --rgp-color-status-foreground: white;
--rgp-color-status-open: rgb(31, 136, 61);
--rgp-color-status-closed: rgb(130, 80, 223);
--rgp-color-status-error: red;
- --rgp-color-reaction-foreground: #0969da;
+ --rgp-color-reaction-foreground: #0969da;
--rgp-color-reaction-background: #ddf4ff;
--rgp-color-button-text: #59636e;
@@ -44,43 +42,43 @@
.rgp-base {
- --rgp-color-bg-stark: rgb(29, 31, 33);
- --rgp-color-text-stark: rgb(230, 237, 243);
+ --rgp-color-bg-stark: rgb(29, 31, 33);
+ --rgp-color-text-stark: rgb(230, 237, 243);
-
- --rgp-color-bg-frame: rgb(22, 27, 34);
- --rgp-color-text-frame: rgb(141, 150, 160);
-
- --rgp-color-border: rgb(48, 54, 61);
-
- --rgp-color-issuenumber: rgb(101, 109, 118);
- --rgp-color-file-link: rgb(68, 147, 248);
- --rgp-color-commit-link: rgb(230, 237, 243);
- --rgp-color-status-foreground: white;
- --rgp-color-status-open: #238636;
- --rgp-color-status-closed: rgb(130, 80, 223);
- --rgp-color-status-error: red;
+ --rgp-color-bg-frame: rgb(22, 27, 34);
+ --rgp-color-text-frame: rgb(141, 150, 160);
+
+ --rgp-color-border: rgb(48, 54, 61);
+
+ --rgp-color-issuenumber: rgb(101, 109, 118);
+ --rgp-color-file-link: rgb(68, 147, 248);
+ --rgp-color-commit-link: rgb(230, 237, 243);
+
+ --rgp-color-status-foreground: white;
+ --rgp-color-status-open: #238636;
+ --rgp-color-status-closed: rgb(130, 80, 223);
+ --rgp-color-status-error: red;
+
+ --rgp-color-reaction-foreground: #4493f8;
+ --rgp-color-reaction-background: #388bfd33;
+
- --rgp-color-reaction-foreground: #4493f8;
- --rgp-color-reaction-background: #388bfd33;
+ --rgp-color-button-text: #9198a1;
+ --rgp-color-button-background: #212830;
+ --rgp-color-button-background-hover: #262c36;
-
- --rgp-color-button-text: #9198a1;
- --rgp-color-button-background: #212830;
- --rgp-color-button-background-hover: #262c36;
+ --rgp-color-button-border: #3d444d;
- --rgp-color-button-border: #3d444d;
-
}
}
.react-github-issue-link-inline {
- color: var(--rgp-color-reaction-forergound);
- border: solid 1px var(--rgp-color-text-frame);
+ color: var(--rgp-color-reaction-forergound);
+ border: solid 1px var(--rgp-color-text-frame);
border-radius: 2px;
- background-color: var(--rgp-color-bg-stark);
+ background-color: var(--rgp-color-bg-stark);
font-family: monospace;
padding: 0 0.25em;
margin: 0 0.25em;
@@ -88,28 +86,33 @@
.github-logo {
- position: relative;
+ position: relative;
top: 0.1em;
- width: 0.9em;
- height: 0.9em;
+ width: 0.9em;
+ height: 0.9em;
}
}
.rgp-base {
- pre + .hide-line-numbers {
+ &>pre {
+ /* react-style-highlighter is adding margin via style attribute*/
+ margin: 0 !important;
+ }
+
+ pre+.hide-line-numbers {
margin-top: -1em;
}
- .hide-line-numbers + pre {
+ .hide-line-numbers+pre {
margin-top: 0em;
}
.hide-line-numbers {
- margin:0;
+ margin: 0;
}
-
+
.hide-line-numbers .linenumber {
visibility: hidden;
}
@@ -119,19 +122,20 @@
-svg.github-logo{
+svg.github-logo {
fill: var(--rgp-color-commit-link);
}
.react-github-issuelink .react-github-issuelink-body {
font-size: 16px;
color: var(--rgp-color-text-stark);
- display: flex;
- flex-flow: row nowrap;
+ display: flex;
+ flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
text-decoration: none;
}
+
.react-github-issuelink-body p {
margin: 0;
}
@@ -143,7 +147,7 @@ svg.github-logo{
}
.react-github-issuelink a:hover .react-github-issuelink-title,
- .react-github-issuelink a:hover .react-github-issuelink-number {
+.react-github-issuelink a:hover .react-github-issuelink-number {
text-decoration: underline;
}
@@ -153,7 +157,7 @@ svg.github-logo{
}
.react-github-issuelink-repo {
- font-size: 12px;
+ font-size: 12px;
font-weight: bold;
display: flex;
flex-flow: row nowrap;
@@ -162,18 +166,18 @@ svg.github-logo{
text-decoration: underline;
}
-.react-github-issuelink-status {
- color: var(--rgp-color-status-foreground);
- fill: var(--rgp-color-status-foreground);
+.react-github-issuelink-status {
+ color: var(--rgp-color-status-foreground);
+ fill: var(--rgp-color-status-foreground);
font-size: 0.75em;
- padding: 0 10px;
- border-radius: 2em;
+ padding: 0 10px;
+ border-radius: 2em;
font-weight: 500;
- line-height: 24px;
+ line-height: 24px;
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
justify-content: space-between;
gap: 0.6em;
@@ -191,26 +195,28 @@ svg.github-logo{
padding: 0.5em;
}
-.react-github-permalink, .react-github-issuelink {
+.react-github-permalink,
+.react-github-issuelink {
border: solid 1px var(--rgp-color-border);
margin: 0.5em;
font-size: 12px;
- border-radius: 4px;
+ border-radius: 4px;
text-align: left;
background-color: var(--rgp-color-bg-stark);
}
-.react-github-permalink, .react-github-permalink .header {
+.react-github-permalink,
+.react-github-permalink .header {
border-color: var(--rgp-color-border);
- border-style: solid;
- border-width: 1px;
+ border-style: solid;
+ border-width: 1px;
}
.react-github-permalink .header {
- display: flex;
+ display: flex;
flex-flow: row nowrap;
gap: 0.75em;
align-items: center;
@@ -218,113 +224,116 @@ svg.github-logo{
.copy-button-container {
- .tooltip-container{
- position: relative;
- anchor-name: --tooltip-anchor;
+ .tooltip-container {
+ position: relative;
+ anchor-name: --tooltip-anchor;
+ }
+
+ .tooltip-content {
+
+ position: absolute;
+ top: -14px;
+ left: -26px;
+ background-color: var(--rgp-color-tooltip-bg);
+
+ transition-property: opacity;
+ transition-duration: 0.25s;
+ opacity: 1;
+
+ @starting-style {
+ opacity: 0;
}
- .tooltip-content {
+ @supports(position-anchor: --tooltip-anchor) {
+ position: fixed;
+ position-anchor: --tooltip-anchor;
+ top: unset;
+ left: unset;
+ bottom: anchor(top);
+ justify-self: anchor-center;
+ margin: 3px;
- position: absolute;
- top: -14px;
- left: -26px;
- background-color: var(--rgp-color-tooltip-bg);
+ }
- transition-property: opacity;
- transition-duration: 0.25s;
- opacity: 1;
- @starting-style {
- opacity: 0;
- }
+ height: 12px;
- @supports(position-anchor: --tooltip-anchor){
- position: fixed;
- position-anchor: --tooltip-anchor;
- top:unset;
- left: unset;
- bottom: anchor(top);
- justify-self: anchor-center;
- margin: 3px;
- }
+ white-space: nowrap;
+ border-radius: 2px;
+ color: var(--rgp-color-text-stark);
+ box-shadow: 2px 2px 2px var(--rgp-color-bg-stark);
- height: 12px;
-
-
- white-space: nowrap;
- border-radius: 2px;
+ font-size: 11px;
+ padding: 0.5em 0.75em;
+ }
- color: var(--rgp-color-text-stark);
- box-shadow: 2px 2px 2px var(--rgp-color-bg-stark);
+ .tooltip-target[popover] {
+ overlay: none !important;
+ display: none;
+ }
- font-size: 11px;
- padding: 0.5em 0.75em;
- }
+ .tooltip-target[popover]:popover-open+.tooltip-content {
+ display: block;
+ }
- .tooltip-target[popover] {
- overlay: none !important;
- display: none;
- }
+ .tooltip-target[popover]+.tooltip-content {
+ display: none;
+ }
- .tooltip-target[popover]:popover-open + .tooltip-content {
- display: block;
+
+
+ button {
+ all: unset;
+ /* Remove default styling */
+ appearance: none;
+ /* Remove specific styling across different browsers */
+ border: none;
+ /* Remove any border */
+ background: none;
+ /* Remove background */
+ padding: 0;
+ /* Remove default padding */
+ margin: 0;
+ /* Remove default margin */
+ box-shadow: none;
+ /* Remove box shadow */
+ text-align: inherit;
+ /* Inherit text alignment from parent */
+ font: inherit;
+ /* Use parent's font settings */
+ color: inherit;
+ /* Use parent's text color */
+ cursor: pointer;
+ /* Set pointer cursor for better UX */
+ background-color: var(--rgp-color-button-background);
+ border: solid 1px var(--rgp-color-button-border);
+ padding: 0.5em;
+ border-radius: 0.25em;
+
+
+ color: var(--rgp-color-button-text);
+
+ &.clicked {
+ color: var(--rgp-color-button-text-success);
}
- .tooltip-target[popover] + .tooltip-content {
- display: none;
+ svg {
+ fill: currentColor;
}
+ &:hover {
+ background-color: var(--rgp-color-button-background-hover);
+ }
+ }
- button {
- all: unset;
- /* Remove default styling */
- appearance: none;
- /* Remove specific styling across different browsers */
- border: none;
- /* Remove any border */
- background: none;
- /* Remove background */
- padding: 0;
- /* Remove default padding */
- margin: 0;
- /* Remove default margin */
- box-shadow: none;
- /* Remove box shadow */
- text-align: inherit;
- /* Inherit text alignment from parent */
- font: inherit;
- /* Use parent's font settings */
- color: inherit;
- /* Use parent's text color */
- cursor: pointer;
- /* Set pointer cursor for better UX */
- background-color: var(--rgp-color-button-background);
- border: solid 1px var(--rgp-color-button-border);
- padding: 0.5em;
- border-radius: 0.25em;
-
-
- color: var(--rgp-color-button-text);
-
- &.clicked {
- color: var(--rgp-color-button-text-success);
- }
- svg {
- fill: currentColor;
- }
- &:hover {
- background-color: var(--rgp-color-button-background-hover);
- }
-
- }
-
}
-.react-github-permalink .header, .react-github-issuelink .header{
- border-width: 0 0 1px 0;
+.react-github-permalink .header,
+.react-github-issuelink .header {
+ border-width: 0 0 1px 0;
font-family: "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
}
@@ -337,7 +346,7 @@ svg.github-logo{
}
.react-github-permalink a.commit-link {
- color: var(--rgp-color-commit-link);
+ color: var(--rgp-color-commit-link);
text-decoration: underline;
}
@@ -351,10 +360,10 @@ svg.github-logo{
.react-github-permalink .header {
background-color: var(--rgp-color-bg-frame);
- padding: 8px;
+ padding: 8px;
.link-wrapper {
- min-width: 0;
+ min-width: 0;
overflow: hidden;
flex: 1 1 auto;
@@ -369,28 +378,29 @@ svg.github-logo{
*/
text-overflow: ellipsis;
overflow: hidden;
- display: block;
+ display: block;
}
}
}
-.react-github-permalink .error, .react-github-issuelink .error {
- font-weight: bold;
+.react-github-permalink .error,
+.react-github-issuelink .error {
+ font-weight: bold;
color: var(--rgp-color-status-error);
margin-left: 1em;
}
.reaction-bar {
- font-size: 0.8em;
+ font-size: 0.8em;
margin-top: 0.5em;
}
.reaction-value {
- color: var(--rgp-color-reaction-foreground);
- background-color: var(--rgp-color-reaction-background);
- border: solid 1px var(--rgp-color-reaction-foreground);
+ color: var(--rgp-color-reaction-foreground);
+ background-color: var(--rgp-color-reaction-background);
+ border: solid 1px var(--rgp-color-reaction-foreground);
border-radius: 100px;
display: inline-block;
margin-right: 0.75em;
@@ -399,4 +409,4 @@ svg.github-logo{
span {
margin: 0 2px;
}
-}
+}
\ No newline at end of file
diff --git a/src/library/SyntaxHighlight/SyntaxHighlight.tsx b/src/library/SyntaxHighlight/SyntaxHighlight.tsx
index c2ebe3f..5a1a8de 100644
--- a/src/library/SyntaxHighlight/SyntaxHighlight.tsx
+++ b/src/library/SyntaxHighlight/SyntaxHighlight.tsx
@@ -1,8 +1,10 @@
"use client";
-import ReactSyntaxHighlighter from "react-syntax-highlighter/dist/esm/default-highlight";
-import { github, tomorrowNight } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { useMediaQuery } from "react-responsive";
import { useEffect, useState } from "react";
+import { AvailableLanguagesPrism } from './availableLanguagesPrism';
+import language from 'react-syntax-highlighter/dist/esm/languages/hljs/1c';
const noTheme = {
"hljs": {
@@ -125,10 +127,11 @@ const noTheme = {
export function SyntaxHighlight(props: {
text: string;
startingLineNumber?: number;
- className?: string;
+ className?: string;
+ language?: AvailableLanguagesPrism
}) {
- const { startingLineNumber, text, className } = props;
+ const { startingLineNumber, text, className, language } = props;
const isDarkMode = useMediaQuery({ query: "(prefers-color-scheme: dark)" })
@@ -141,6 +144,6 @@ export function SyntaxHighlight(props: {
}, [])
- return {text}
+ return {text}
}
diff --git a/src/library/SyntaxHighlight/availableLanguagesPrism.ts b/src/library/SyntaxHighlight/availableLanguagesPrism.ts
new file mode 100644
index 0000000..f302403
--- /dev/null
+++ b/src/library/SyntaxHighlight/availableLanguagesPrism.ts
@@ -0,0 +1,282 @@
+export type AvailableLanguagesPrism =
+ | "abap"
+ | "abnf"
+ | "actionscript"
+ | "ada"
+ | "agda"
+ | "al"
+ | "antlr4"
+ | "apacheconf"
+ | "apex"
+ | "apl"
+ | "applescript"
+ | "aql"
+ | "arduino"
+ | "arff"
+ | "asciidoc"
+ | "asm6502"
+ | "asmatmel"
+ | "aspnet"
+ | "autohotkey"
+ | "autoit"
+ | "avisynth"
+ | "avroIdl" // avro-idl
+ | "bash"
+ | "basic"
+ | "batch"
+ | "bbcode"
+ | "bicep"
+ | "birb"
+ | "bison"
+ | "bnf"
+ | "brainfuck"
+ | "brightscript"
+ | "bro"
+ | "bsl"
+ | "c"
+ | "cfscript"
+ | "chaiscript"
+ | "cil"
+ | "clike"
+ | "clojure"
+ | "cmake"
+ | "cobol"
+ | "coffeescript"
+ | "concurnas"
+ | "coq"
+ | "cpp"
+ | "crystal"
+ | "csharp"
+ | "cshtml"
+ | "csp"
+ | "cssExtras" // css-extras
+ | "css"
+ | "csv"
+ | "cypher"
+ | "d"
+ | "dart"
+ | "dataweave"
+ | "dax"
+ | "dhall"
+ | "diff"
+ | "django"
+ | "dnsZoneFile" // dns-zone-file
+ | "docker"
+ | "dot"
+ | "ebnf"
+ | "editorconfig"
+ | "eiffel"
+ | "ejs"
+ | "elixir"
+ | "elm"
+ | "erb"
+ | "erlang"
+ | "etlua"
+ | "excelFormula" // excel-formula
+ | "factor"
+ | "falselang" // false
+ | "firestoreSecurityRules" // firestore-security-rules
+ | "flow"
+ | "fortran"
+ | "fsharp"
+ | "ftl"
+ | "gap"
+ | "gcode"
+ | "gdscript"
+ | "gedcom"
+ | "gherkin"
+ | "git"
+ | "glsl"
+ | "gml"
+ | "gn"
+ | "goModule" // go-module
+ | "go"
+ | "graphql"
+ | "groovy"
+ | "haml"
+ | "handlebars"
+ | "haskell"
+ | "haxe"
+ | "hcl"
+ | "hlsl"
+ | "hoon"
+ | "hpkp"
+ | "hsts"
+ | "http"
+ | "ichigojam"
+ | "icon"
+ | "icuMessageFormat" // icu-message-format
+ | "idris"
+ | "iecst"
+ | "ignore"
+ | "inform7"
+ | "ini"
+ | "io"
+ | "j"
+ | "java"
+ | "javadoc"
+ | "javadoclike"
+ | "javascript"
+ | "javastacktrace"
+ | "jexl"
+ | "jolie"
+ | "jq"
+ | "jsExtras" // js-extras
+ | "jsTemplates" // js-templates
+ | "jsdoc"
+ | "json"
+ | "json5"
+ | "jsonp"
+ | "jsstacktrace"
+ | "jsx"
+ | "julia"
+ | "keepalived"
+ | "keyman"
+ | "kotlin"
+ | "kumir"
+ | "kusto"
+ | "latex"
+ | "latte"
+ | "less"
+ | "lilypond"
+ | "liquid"
+ | "lisp"
+ | "livescript"
+ | "llvm"
+ | "log"
+ | "lolcode"
+ | "lua"
+ | "magma"
+ | "makefile"
+ | "markdown"
+ | "markupTemplating" // markup-templating
+ | "markup"
+ | "matlab"
+ | "maxscript"
+ | "mel"
+ | "mermaid"
+ | "mizar"
+ | "mongodb"
+ | "monkey"
+ | "moonscript"
+ | "n1ql"
+ | "n4js"
+ | "nand2tetrisHdl" // nand2tetris-hdl
+ | "naniscript"
+ | "nasm"
+ | "neon"
+ | "nevod"
+ | "nginx"
+ | "nim"
+ | "nix"
+ | "nsis"
+ | "objectivec"
+ | "ocaml"
+ | "opencl"
+ | "openqasm"
+ | "oz"
+ | "parigp"
+ | "parser"
+ | "pascal"
+ | "pascaligo"
+ | "pcaxis"
+ | "peoplecode"
+ | "perl"
+ | "phpExtras" // php-extras
+ | "php"
+ | "phpdoc"
+ | "plsql"
+ | "powerquery"
+ | "powershell"
+ | "processing"
+ | "prolog"
+ | "promql"
+ | "properties"
+ | "protobuf"
+ | "psl"
+ | "pug"
+ | "puppet"
+ | "pure"
+ | "purebasic"
+ | "purescript"
+ | "python"
+ | "q"
+ | "qml"
+ | "qore"
+ | "qsharp"
+ | "r"
+ | "racket"
+ | "reason"
+ | "regex"
+ | "rego"
+ | "renpy"
+ | "rest"
+ | "rip"
+ | "roboconf"
+ | "robotframework"
+ | "ruby"
+ | "rust"
+ | "sas"
+ | "sass"
+ | "scala"
+ | "scheme"
+ | "scss"
+ | "shellSession" // shell-session
+ | "smali"
+ | "smalltalk"
+ | "smarty"
+ | "sml"
+ | "solidity"
+ | "solutionFile" // solution-file
+ | "soy"
+ | "sparql"
+ | "splunkSpl" // splunk-spl
+ | "sqf"
+ | "sql"
+ | "squirrel"
+ | "stan"
+ | "stylus"
+ | "swift"
+ | "systemd"
+ | "t4Cs" // t4-cs
+ | "t4Templating" // t4-templating
+ | "t4Vb" // t4-vb
+ | "tap"
+ | "tcl"
+ | "textile"
+ | "toml"
+ | "tremor"
+ | "tsx"
+ | "tt2"
+ | "turtle"
+ | "twig"
+ | "typescript"
+ | "typoscript"
+ | "unrealscript"
+ | "uorazor"
+ | "uri"
+ | "v"
+ | "vala"
+ | "vbnet"
+ | "velocity"
+ | "verilog"
+ | "vhdl"
+ | "vim"
+ | "visualBasic" // visual-basic
+ | "warpscript"
+ | "wasm"
+ | "webIdl" // web-idl
+ | "wiki"
+ | "wolfram"
+ | "wren"
+ | "xeora"
+ | "xmlDoc" // xml-doc
+ | "xojo"
+ | "xquery"
+ | "yaml"
+ | "yang"
+ | "zig"
+ | "xml"
+ | "html"
+ | "dockerfile"
+ | "text";
\ No newline at end of file
diff --git a/src/library/utils/getLanguageFromPath.test.ts b/src/library/utils/getLanguageFromPath.test.ts
new file mode 100644
index 0000000..e7165c8
--- /dev/null
+++ b/src/library/utils/getLanguageFromPath.test.ts
@@ -0,0 +1,69 @@
+import { describe, it, expect } from 'vitest';
+import { getLanguageFromPath } from './getLanguageFromPath';
+
+describe('getLanguageFromPath', () => {
+ it('should return typescript for .tsx files', () => {
+ expect(getLanguageFromPath('ReactRenders3.tsx')).toBe('typescript');
+ });
+
+ it('should return typescript for .ts files', () => {
+ expect(getLanguageFromPath('component.ts')).toBe('typescript');
+ });
+
+ it('should return javascript for .js files', () => {
+ expect(getLanguageFromPath('script.js')).toBe('javascript');
+ });
+
+ it('should return javascript for .jsx files', () => {
+ expect(getLanguageFromPath('component.jsx')).toBe('javascript');
+ });
+
+ it('should return python for .py files', () => {
+ expect(getLanguageFromPath('script.py')).toBe('python');
+ });
+
+ it('should return go for .go files', () => {
+ expect(getLanguageFromPath('main.go')).toBe('go');
+ });
+
+ it('should return html for .html files', () => {
+ expect(getLanguageFromPath('index.html')).toBe('html');
+ });
+
+ it('should return css for .css files', () => {
+ expect(getLanguageFromPath('styles.css')).toBe('css');
+ });
+
+ it('should return json for .json files', () => {
+ expect(getLanguageFromPath('package.json')).toBe('json');
+ });
+
+ it('should return markdown for .md files', () => {
+ expect(getLanguageFromPath('README.md')).toBe('markdown');
+ });
+
+ it('should return yaml for .yaml files', () => {
+ expect(getLanguageFromPath('config.yaml')).toBe('yaml');
+ });
+
+ it('should return yaml for .yml files', () => {
+ expect(getLanguageFromPath('config.yml')).toBe('yaml');
+ });
+
+ it('should return javascript as fallback for unknown extensions', () => {
+ expect(getLanguageFromPath('file.unknown')).toBe('text');
+ });
+
+ it('should return javascript as fallback for files without extension', () => {
+ expect(getLanguageFromPath('asdf')).toBe('text');
+ });
+
+ it('should handle paths with multiple dots', () => {
+ expect(getLanguageFromPath('src/components/MyComponent.tsx')).toBe('typescript');
+ });
+
+ it('should handle case insensitive extensions', () => {
+ expect(getLanguageFromPath('Component.TSX')).toBe('typescript');
+ expect(getLanguageFromPath('Script.JS')).toBe('javascript');
+ });
+});
\ No newline at end of file
diff --git a/src/library/utils/getLanguageFromPath.ts b/src/library/utils/getLanguageFromPath.ts
new file mode 100644
index 0000000..4afca5f
--- /dev/null
+++ b/src/library/utils/getLanguageFromPath.ts
@@ -0,0 +1,90 @@
+import { AvailableLanguagesPrism } from "../SyntaxHighlight/availableLanguagesPrism";
+
+/**
+ * Maps file extensions to syntax highlighting languages supported by react-syntax-highlighter
+ */
+export function getLanguageFromPath(filePath: string): AvailableLanguagesPrism {
+ const extension = filePath.split('.').pop()?.toLowerCase();
+
+ switch (extension) {
+ case 'js':
+ case 'jsx':
+ case 'mjs':
+ case 'cjs':
+ return 'javascript';
+ case 'ts':
+ case 'tsx':
+ return 'typescript';
+ case 'py':
+ case 'python':
+ return 'python';
+ case 'java':
+ return 'java';
+ case 'cpp':
+ case 'cc':
+ case 'cxx':
+ case 'c++':
+ return 'cpp';
+ case 'c':
+ case 'h':
+ return 'c';
+ case 'cs':
+ return 'csharp';
+ case 'php':
+ return 'php';
+ case 'rb':
+ case 'ruby':
+ return 'ruby';
+ case 'go':
+ return 'go';
+ case 'rs':
+ return 'rust';
+ case 'swift':
+ return 'swift';
+ case 'kt':
+ case 'kts':
+ return 'kotlin';
+ case 'scala':
+ return 'scala';
+ case 'sh':
+ case 'bash':
+ return 'bash';
+ case 'ps1':
+ return 'powershell';
+ case 'sql':
+ return 'sql';
+ case 'html':
+ case 'htm':
+ return 'html';
+ case 'css':
+ return 'css';
+ case 'scss':
+ case 'sass':
+ return 'scss';
+ case 'json':
+ return 'json';
+ case 'xml':
+ return 'xml';
+ case 'yaml':
+ case 'yml':
+ return 'yaml';
+ case 'md':
+ case 'markdown':
+ return 'markdown';
+ case 'dockerfile':
+ return 'dockerfile';
+ case 'r':
+ return 'r';
+ case 'dart':
+ return 'dart';
+ case 'lua':
+ return 'lua';
+ case 'perl':
+ case 'pl':
+ return 'perl';
+ case 'vim':
+ return 'vim';
+ default:
+ return 'text'; // fallback to javascript for unknown extensions
+ }
+}
\ No newline at end of file