at://nekomimi.pet/sh.tangled.repo.pull/3lqasymgqb422

Back to Collection

Record JSON

{
  "$type": "sh.tangled.repo.pull",
  "createdAt": "",
  "patch": "From 695bf8098b54d84d75f338238c035fa774caf268 Mon Sep 17 00:00:00 2001\nFrom: waveringana \u003cana@nekomimi.pet\u003e\nDate: Wed, 28 May 2025 14:06:59 -0400\nSubject: [PATCH] add server sided code highlighting from shiki\n\n---\n package-lock.json     | 523 ++++++++++++++++++++++++++++++++++++++++++\n package.json          |   1 +\n src/lib/highlight.ts  |  48 ++++\n src/public/styles.css |  33 +++\n src/routes.ts         |   4 +\n src/views/index.pug   |   2 +\n src/views/paste.pug   |  16 +-\n 7 files changed, 614 insertions(+), 13 deletions(-)\n create mode 100644 src/lib/highlight.ts\n\ndiff --git a/package-lock.json b/package-lock.json\nindex cc16ee6..f67071e 100644\n--- a/package-lock.json\n+++ b/package-lock.json\n@@ -26,6 +26,7 @@\n         \"multiformats\": \"^9.9.0\",\n         \"pino\": \"^9.3.2\",\n         \"pug\": \"^3.0.3\",\n+        \"shiki\": \"^3.4.2\",\n         \"uhtml\": \"^4.5.9\"\n       },\n       \"devDependencies\": {\n@@ -1484,6 +1485,73 @@\n         \"win32\"\n       ]\n     },\n+    \"node_modules/@shikijs/core\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/core/-/core-3.4.2.tgz\",\n+      \"integrity\": \"sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/types\": \"3.4.2\",\n+        \"@shikijs/vscode-textmate\": \"^10.0.2\",\n+        \"@types/hast\": \"^3.0.4\",\n+        \"hast-util-to-html\": \"^9.0.5\"\n+      }\n+    },\n+    \"node_modules/@shikijs/engine-javascript\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.4.2.tgz\",\n+      \"integrity\": \"sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/types\": \"3.4.2\",\n+        \"@shikijs/vscode-textmate\": \"^10.0.2\",\n+        \"oniguruma-to-es\": \"^4.3.3\"\n+      }\n+    },\n+    \"node_modules/@shikijs/engine-oniguruma\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.4.2.tgz\",\n+      \"integrity\": \"sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/types\": \"3.4.2\",\n+        \"@shikijs/vscode-textmate\": \"^10.0.2\"\n+      }\n+    },\n+    \"node_modules/@shikijs/langs\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/langs/-/langs-3.4.2.tgz\",\n+      \"integrity\": \"sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/types\": \"3.4.2\"\n+      }\n+    },\n+    \"node_modules/@shikijs/themes\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/themes/-/themes-3.4.2.tgz\",\n+      \"integrity\": \"sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/types\": \"3.4.2\"\n+      }\n+    },\n+    \"node_modules/@shikijs/types\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/types/-/types-3.4.2.tgz\",\n+      \"integrity\": \"sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/vscode-textmate\": \"^10.0.2\",\n+        \"@types/hast\": \"^3.0.4\"\n+      }\n+    },\n+    \"node_modules/@shikijs/vscode-textmate\": {\n+      \"version\": \"10.0.2\",\n+      \"resolved\": \"https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz\",\n+      \"integrity\": \"sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==\",\n+      \"license\": \"MIT\"\n+    },\n     \"node_modules/@ts-morph/common\": {\n       \"version\": \"0.17.0\",\n       \"resolved\": \"https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz\",\n@@ -1602,6 +1670,15 @@\n         \"@types/send\": \"*\"\n       }\n     },\n+    \"node_modules/@types/hast\": {\n+      \"version\": \"3.0.4\",\n+      \"resolved\": \"https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz\",\n+      \"integrity\": \"sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"*\"\n+      }\n+    },\n     \"node_modules/@types/http-errors\": {\n       \"version\": \"2.0.4\",\n       \"resolved\": \"https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz\",\n@@ -1609,6 +1686,15 @@\n       \"dev\": true,\n       \"license\": \"MIT\"\n     },\n+    \"node_modules/@types/mdast\": {\n+      \"version\": \"4.0.4\",\n+      \"resolved\": \"https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz\",\n+      \"integrity\": \"sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"*\"\n+      }\n+    },\n     \"node_modules/@types/mime\": {\n       \"version\": \"1.3.5\",\n       \"resolved\": \"https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz\",\n@@ -1663,6 +1749,18 @@\n         \"@types/send\": \"*\"\n       }\n     },\n+    \"node_modules/@types/unist\": {\n+      \"version\": \"3.0.3\",\n+      \"resolved\": \"https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz\",\n+      \"integrity\": \"sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==\",\n+      \"license\": \"MIT\"\n+    },\n+    \"node_modules/@ungap/structured-clone\": {\n+      \"version\": \"1.3.0\",\n+      \"resolved\": \"https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz\",\n+      \"integrity\": \"sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==\",\n+      \"license\": \"ISC\"\n+    },\n     \"node_modules/@webreflection/signal\": {\n       \"version\": \"2.1.2\",\n       \"resolved\": \"https://registry.npmjs.org/@webreflection/signal/-/signal-2.1.2.tgz\",\n@@ -2095,6 +2193,16 @@\n         \"cborg\": \"cli.js\"\n       }\n     },\n+    \"node_modules/ccount\": {\n+      \"version\": \"2.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz\",\n+      \"integrity\": \"sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/chalk\": {\n       \"version\": \"4.1.2\",\n       \"resolved\": \"https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz\",\n@@ -2112,6 +2220,26 @@\n         \"url\": \"https://github.com/chalk/chalk?sponsor=1\"\n       }\n     },\n+    \"node_modules/character-entities-html4\": {\n+      \"version\": \"2.1.0\",\n+      \"resolved\": \"https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz\",\n+      \"integrity\": \"sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n+    \"node_modules/character-entities-legacy\": {\n+      \"version\": \"3.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz\",\n+      \"integrity\": \"sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/character-parser\": {\n       \"version\": \"2.2.0\",\n       \"resolved\": \"https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz\",\n@@ -2189,6 +2317,16 @@\n         \"node\": \"\u003e= 0.8\"\n       }\n     },\n+    \"node_modules/comma-separated-tokens\": {\n+      \"version\": \"2.0.3\",\n+      \"resolved\": \"https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz\",\n+      \"integrity\": \"sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/commander\": {\n       \"version\": \"9.5.0\",\n       \"resolved\": \"https://registry.npmjs.org/commander/-/commander-9.5.0.tgz\",\n@@ -2361,6 +2499,15 @@\n         \"node\": \"\u003e= 0.8\"\n       }\n     },\n+    \"node_modules/dequal\": {\n+      \"version\": \"2.0.3\",\n+      \"resolved\": \"https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz\",\n+      \"integrity\": \"sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==\",\n+      \"license\": \"MIT\",\n+      \"engines\": {\n+        \"node\": \"\u003e=6\"\n+      }\n+    },\n     \"node_modules/destroy\": {\n       \"version\": \"1.2.0\",\n       \"resolved\": \"https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz\",\n@@ -2380,6 +2527,19 @@\n         \"node\": \"\u003e=8\"\n       }\n     },\n+    \"node_modules/devlop\": {\n+      \"version\": \"1.1.0\",\n+      \"resolved\": \"https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz\",\n+      \"integrity\": \"sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"dequal\": \"^2.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/diff\": {\n       \"version\": \"4.0.2\",\n       \"resolved\": \"https://registry.npmjs.org/diff/-/diff-4.0.2.tgz\",\n@@ -3043,6 +3203,42 @@\n         \"node\": \"\u003e= 0.4\"\n       }\n     },\n+    \"node_modules/hast-util-to-html\": {\n+      \"version\": \"9.0.5\",\n+      \"resolved\": \"https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz\",\n+      \"integrity\": \"sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/hast\": \"^3.0.0\",\n+        \"@types/unist\": \"^3.0.0\",\n+        \"ccount\": \"^2.0.0\",\n+        \"comma-separated-tokens\": \"^2.0.0\",\n+        \"hast-util-whitespace\": \"^3.0.0\",\n+        \"html-void-elements\": \"^3.0.0\",\n+        \"mdast-util-to-hast\": \"^13.0.0\",\n+        \"property-information\": \"^7.0.0\",\n+        \"space-separated-tokens\": \"^2.0.0\",\n+        \"stringify-entities\": \"^4.0.0\",\n+        \"zwitch\": \"^2.0.4\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/hast-util-whitespace\": {\n+      \"version\": \"3.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz\",\n+      \"integrity\": \"sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/hast\": \"^3.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n     \"node_modules/help-me\": {\n       \"version\": \"5.0.0\",\n       \"resolved\": \"https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz\",\n@@ -3056,6 +3252,16 @@\n       \"integrity\": \"sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==\",\n       \"license\": \"MIT\"\n     },\n+    \"node_modules/html-void-elements\": {\n+      \"version\": \"3.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz\",\n+      \"integrity\": \"sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/htmlparser2\": {\n       \"version\": \"9.1.0\",\n       \"resolved\": \"https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz\",\n@@ -3404,6 +3610,27 @@\n       \"dev\": true,\n       \"license\": \"ISC\"\n     },\n+    \"node_modules/mdast-util-to-hast\": {\n+      \"version\": \"13.2.0\",\n+      \"resolved\": \"https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz\",\n+      \"integrity\": \"sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/hast\": \"^3.0.0\",\n+        \"@types/mdast\": \"^4.0.0\",\n+        \"@ungap/structured-clone\": \"^1.0.0\",\n+        \"devlop\": \"^1.0.0\",\n+        \"micromark-util-sanitize-uri\": \"^2.0.0\",\n+        \"trim-lines\": \"^3.0.0\",\n+        \"unist-util-position\": \"^5.0.0\",\n+        \"unist-util-visit\": \"^5.0.0\",\n+        \"vfile\": \"^6.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n     \"node_modules/media-typer\": {\n       \"version\": \"0.3.0\",\n       \"resolved\": \"https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz\",\n@@ -3441,6 +3668,95 @@\n         \"node\": \"\u003e= 0.6\"\n       }\n     },\n+    \"node_modules/micromark-util-character\": {\n+      \"version\": \"2.1.1\",\n+      \"resolved\": \"https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz\",\n+      \"integrity\": \"sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==\",\n+      \"funding\": [\n+        {\n+          \"type\": \"GitHub Sponsors\",\n+          \"url\": \"https://github.com/sponsors/unifiedjs\"\n+        },\n+        {\n+          \"type\": \"OpenCollective\",\n+          \"url\": \"https://opencollective.com/unified\"\n+        }\n+      ],\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"micromark-util-symbol\": \"^2.0.0\",\n+        \"micromark-util-types\": \"^2.0.0\"\n+      }\n+    },\n+    \"node_modules/micromark-util-encode\": {\n+      \"version\": \"2.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz\",\n+      \"integrity\": \"sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==\",\n+      \"funding\": [\n+        {\n+          \"type\": \"GitHub Sponsors\",\n+          \"url\": \"https://github.com/sponsors/unifiedjs\"\n+        },\n+        {\n+          \"type\": \"OpenCollective\",\n+          \"url\": \"https://opencollective.com/unified\"\n+        }\n+      ],\n+      \"license\": \"MIT\"\n+    },\n+    \"node_modules/micromark-util-sanitize-uri\": {\n+      \"version\": \"2.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz\",\n+      \"integrity\": \"sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==\",\n+      \"funding\": [\n+        {\n+          \"type\": \"GitHub Sponsors\",\n+          \"url\": \"https://github.com/sponsors/unifiedjs\"\n+        },\n+        {\n+          \"type\": \"OpenCollective\",\n+          \"url\": \"https://opencollective.com/unified\"\n+        }\n+      ],\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"micromark-util-character\": \"^2.0.0\",\n+        \"micromark-util-encode\": \"^2.0.0\",\n+        \"micromark-util-symbol\": \"^2.0.0\"\n+      }\n+    },\n+    \"node_modules/micromark-util-symbol\": {\n+      \"version\": \"2.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz\",\n+      \"integrity\": \"sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==\",\n+      \"funding\": [\n+        {\n+          \"type\": \"GitHub Sponsors\",\n+          \"url\": \"https://github.com/sponsors/unifiedjs\"\n+        },\n+        {\n+          \"type\": \"OpenCollective\",\n+          \"url\": \"https://opencollective.com/unified\"\n+        }\n+      ],\n+      \"license\": \"MIT\"\n+    },\n+    \"node_modules/micromark-util-types\": {\n+      \"version\": \"2.0.2\",\n+      \"resolved\": \"https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz\",\n+      \"integrity\": \"sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==\",\n+      \"funding\": [\n+        {\n+          \"type\": \"GitHub Sponsors\",\n+          \"url\": \"https://github.com/sponsors/unifiedjs\"\n+        },\n+        {\n+          \"type\": \"OpenCollective\",\n+          \"url\": \"https://opencollective.com/unified\"\n+        }\n+      ],\n+      \"license\": \"MIT\"\n+    },\n     \"node_modules/micromatch\": {\n       \"version\": \"4.0.8\",\n       \"resolved\": \"https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz\",\n@@ -3671,6 +3987,23 @@\n         \"wrappy\": \"1\"\n       }\n     },\n+    \"node_modules/oniguruma-parser\": {\n+      \"version\": \"0.12.1\",\n+      \"resolved\": \"https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz\",\n+      \"integrity\": \"sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==\",\n+      \"license\": \"MIT\"\n+    },\n+    \"node_modules/oniguruma-to-es\": {\n+      \"version\": \"4.3.3\",\n+      \"resolved\": \"https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz\",\n+      \"integrity\": \"sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"oniguruma-parser\": \"^0.12.1\",\n+        \"regex\": \"^6.0.1\",\n+        \"regex-recursion\": \"^6.0.2\"\n+      }\n+    },\n     \"node_modules/p-finally\": {\n       \"version\": \"1.0.0\",\n       \"resolved\": \"https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz\",\n@@ -3972,6 +4305,16 @@\n         \"asap\": \"~2.0.3\"\n       }\n     },\n+    \"node_modules/property-information\": {\n+      \"version\": \"7.1.0\",\n+      \"resolved\": \"https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz\",\n+      \"integrity\": \"sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/proxy-addr\": {\n       \"version\": \"2.0.7\",\n       \"resolved\": \"https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz\",\n@@ -4284,6 +4627,30 @@\n         \"node\": \"\u003e= 12.13.0\"\n       }\n     },\n+    \"node_modules/regex\": {\n+      \"version\": \"6.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/regex/-/regex-6.0.1.tgz\",\n+      \"integrity\": \"sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"regex-utilities\": \"^2.3.0\"\n+      }\n+    },\n+    \"node_modules/regex-recursion\": {\n+      \"version\": \"6.0.2\",\n+      \"resolved\": \"https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz\",\n+      \"integrity\": \"sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"regex-utilities\": \"^2.3.0\"\n+      }\n+    },\n+    \"node_modules/regex-utilities\": {\n+      \"version\": \"2.3.0\",\n+      \"resolved\": \"https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz\",\n+      \"integrity\": \"sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==\",\n+      \"license\": \"MIT\"\n+    },\n     \"node_modules/resolve\": {\n       \"version\": \"1.22.8\",\n       \"resolved\": \"https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz\",\n@@ -4564,6 +4931,22 @@\n         \"node\": \"\u003e=8\"\n       }\n     },\n+    \"node_modules/shiki\": {\n+      \"version\": \"3.4.2\",\n+      \"resolved\": \"https://registry.npmjs.org/shiki/-/shiki-3.4.2.tgz\",\n+      \"integrity\": \"sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@shikijs/core\": \"3.4.2\",\n+        \"@shikijs/engine-javascript\": \"3.4.2\",\n+        \"@shikijs/engine-oniguruma\": \"3.4.2\",\n+        \"@shikijs/langs\": \"3.4.2\",\n+        \"@shikijs/themes\": \"3.4.2\",\n+        \"@shikijs/types\": \"3.4.2\",\n+        \"@shikijs/vscode-textmate\": \"^10.0.2\",\n+        \"@types/hast\": \"^3.0.4\"\n+      }\n+    },\n     \"node_modules/side-channel\": {\n       \"version\": \"1.0.6\",\n       \"resolved\": \"https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz\",\n@@ -4662,6 +5045,16 @@\n         \"node\": \"\u003e= 8\"\n       }\n     },\n+    \"node_modules/space-separated-tokens\": {\n+      \"version\": \"2.0.2\",\n+      \"resolved\": \"https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz\",\n+      \"integrity\": \"sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/split2\": {\n       \"version\": \"4.2.0\",\n       \"resolved\": \"https://registry.npmjs.org/split2/-/split2-4.2.0.tgz\",\n@@ -4753,6 +5146,20 @@\n         \"node\": \"\u003e=8\"\n       }\n     },\n+    \"node_modules/stringify-entities\": {\n+      \"version\": \"4.0.4\",\n+      \"resolved\": \"https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz\",\n+      \"integrity\": \"sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"character-entities-html4\": \"^2.0.0\",\n+        \"character-entities-legacy\": \"^3.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/strip-ansi\": {\n       \"version\": \"7.1.0\",\n       \"resolved\": \"https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz\",\n@@ -5044,6 +5451,16 @@\n         \"tree-kill\": \"cli.js\"\n       }\n     },\n+    \"node_modules/trim-lines\": {\n+      \"version\": \"3.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz\",\n+      \"integrity\": \"sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n+    },\n     \"node_modules/ts-interface-checker\": {\n       \"version\": \"0.1.13\",\n       \"resolved\": \"https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz\",\n@@ -5752,6 +6169,74 @@\n       \"dev\": true,\n       \"license\": \"MIT\"\n     },\n+    \"node_modules/unist-util-is\": {\n+      \"version\": \"6.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz\",\n+      \"integrity\": \"sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/unist-util-position\": {\n+      \"version\": \"5.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz\",\n+      \"integrity\": \"sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/unist-util-stringify-position\": {\n+      \"version\": \"4.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz\",\n+      \"integrity\": \"sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/unist-util-visit\": {\n+      \"version\": \"5.0.0\",\n+      \"resolved\": \"https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz\",\n+      \"integrity\": \"sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\",\n+        \"unist-util-is\": \"^6.0.0\",\n+        \"unist-util-visit-parents\": \"^6.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/unist-util-visit-parents\": {\n+      \"version\": \"6.0.1\",\n+      \"resolved\": \"https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz\",\n+      \"integrity\": \"sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\",\n+        \"unist-util-is\": \"^6.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n     \"node_modules/unpipe\": {\n       \"version\": \"1.0.0\",\n       \"resolved\": \"https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz\",\n@@ -5798,6 +6283,34 @@\n         \"node\": \"\u003e= 0.8\"\n       }\n     },\n+    \"node_modules/vfile\": {\n+      \"version\": \"6.0.3\",\n+      \"resolved\": \"https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz\",\n+      \"integrity\": \"sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\",\n+        \"vfile-message\": \"^4.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n+    \"node_modules/vfile-message\": {\n+      \"version\": \"4.0.2\",\n+      \"resolved\": \"https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz\",\n+      \"integrity\": \"sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==\",\n+      \"license\": \"MIT\",\n+      \"dependencies\": {\n+        \"@types/unist\": \"^3.0.0\",\n+        \"unist-util-stringify-position\": \"^4.0.0\"\n+      },\n+      \"funding\": {\n+        \"type\": \"opencollective\",\n+        \"url\": \"https://opencollective.com/unified\"\n+      }\n+    },\n     \"node_modules/void-elements\": {\n       \"version\": \"3.1.0\",\n       \"resolved\": \"https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz\",\n@@ -6004,6 +6517,16 @@\n       \"funding\": {\n         \"url\": \"https://github.com/sponsors/colinhacks\"\n       }\n+    },\n+    \"node_modules/zwitch\": {\n+      \"version\": \"2.0.4\",\n+      \"resolved\": \"https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz\",\n+      \"integrity\": \"sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==\",\n+      \"license\": \"MIT\",\n+      \"funding\": {\n+        \"type\": \"github\",\n+        \"url\": \"https://github.com/sponsors/wooorm\"\n+      }\n     }\n   }\n }\ndiff --git a/package.json b/package.json\nindex 23262d2..005e46b 100644\n--- a/package.json\n+++ b/package.json\n@@ -30,6 +30,7 @@\n     \"multiformats\": \"^9.9.0\",\n     \"pino\": \"^9.3.2\",\n     \"pug\": \"^3.0.3\",\n+    \"shiki\": \"^3.4.2\",\n     \"uhtml\": \"^4.5.9\"\n   },\n   \"devDependencies\": {\ndiff --git a/src/lib/highlight.ts b/src/lib/highlight.ts\nnew file mode 100644\nindex 0000000..4aa7d90\n--- /dev/null\n+++ b/src/lib/highlight.ts\n@@ -0,0 +1,48 @@\n+import { createHighlighter, HighlighterGeneric, BundledLanguage, BundledTheme } from 'shiki'\n+import type { Element } from 'hast'\n+\n+let highlighter: HighlighterGeneric\u003cBundledLanguage, BundledTheme\u003e | null = null\n+\n+export async function getHighlighterInstance() {\n+  if (!highlighter) {\n+    highlighter = await createHighlighter({\n+      themes: ['catppuccin-latte', 'catppuccin-mocha'],\n+      langs: [\n+        'javascript', 'typescript', 'python', 'java', 'rust', \n+        'go', 'c', 'cpp', 'csharp', 'php', 'ruby', 'cobol',\n+        'plaintext', 'txt', 'bash', 'nix'\n+      ]\n+    })\n+  }\n+  return highlighter\n+}\n+\n+export async function highlightCode(code: string, lang: string) {\n+  const highlighter = await getHighlighterInstance()\n+  \n+  try {\n+    return highlighter.codeToHtml(code, {\n+      lang: lang,\n+      themes: {\n+        light: 'catppuccin-latte',\n+        dark: 'catppuccin-mocha'\n+      },\n+      transformers: [\n+        {\n+          line(node: Element, line: number) {\n+            node.properties['code-line'] = line\n+          }\n+        }\n+      ]\n+    })\n+  } catch (error) {\n+    console.warn(`Language \"${lang}\" not supported by Shiki, falling back to plaintext`)\n+    return highlighter.codeToHtml(code, {\n+      lang: 'txt',\n+      themes: {\n+        light: 'catppuccin-latte',\n+        dark: 'catppuccin-mocha'\n+      }\n+    })\n+  }\n+} \n\\ No newline at end of file\ndiff --git a/src/public/styles.css b/src/public/styles.css\nindex b153f92..61d3019 100644\n--- a/src/public/styles.css\n+++ b/src/public/styles.css\n@@ -231,3 +231,36 @@ select {\n .footer a:hover {\n   text-decoration: underline;\n }\n+\n+.highlighted-code {\n+  margin: 1rem 0;\n+  counter-reset: step;\n+  counter-increment: step calc(var(--start, 1) - 1);\n+}\n+\n+.highlighted-code pre {\n+  padding: 1rem;\n+  overflow-x: auto;\n+  border-radius: 4px;\n+}\n+\n+code .line::before {\n+  content: counter(step);\n+  counter-increment: step;\n+  width: 1rem;\n+  margin-right: 1.5rem;\n+  display: inline-block;\n+  text-align: right;\n+  color: rgba(115,138,148,.4)\n+}\n+\n+@media (prefers-color-scheme: dark) {\n+  .shiki,\n+  .shiki span {\n+    color: var(--shiki-dark) !important;\n+    background-color: var(--shiki-dark-bg) !important;\n+    font-style: var(--shiki-dark-font-style) !important;\n+    font-weight: var(--shiki-dark-font-weight) !important;\n+    text-decoration: var(--shiki-dark-text-decoration) !important;\n+  }\n+}\n\\ No newline at end of file\ndiff --git a/src/routes.ts b/src/routes.ts\nindex 17fa00e..e4782ca 100644\n--- a/src/routes.ts\n+++ b/src/routes.ts\n@@ -11,6 +11,7 @@ import { newShortUrl } from \"#/db\";\n \n import * as Paste from \"#/lexicons/types/li/plonk/paste\";\n import * as Comment from \"#/lexicons/types/li/plonk/comment\";\n+import { highlightCode } from \"#/lib/highlight\";\n \n type Session = {\n \tdid: string;\n@@ -209,9 +210,12 @@ export const createRouter = (ctx: Ctx) =\u003e {\n \t\t\t),\n \t\t);\n \n+\t\tconst highlightedCode = await highlightCode(pasteCode, pasteLang);\n+\n \t\tconst paste = {\n \t\t\turi: pasteUri,\n \t\t\tcode: pasteCode,\n+\t\t\thighlightedCode,\n \t\t\ttitle: pasteTitle,\n \t\t\tlang: pasteLang,\n \t\t\tshortUrl,\ndiff --git a/src/views/index.pug b/src/views/index.pug\nindex bc9085a..17ddcbc 100644\n--- a/src/views/index.pug\n+++ b/src/views/index.pug\n@@ -9,6 +9,7 @@ include ../mixins/post\n - \n   var langs = [\"plaintext\"]\n       .concat([\n+        \"bash\",\n         \"javascript\",\n         \"typescript\",\n         \"java\",\n@@ -21,6 +22,7 @@ include ../mixins/post\n         \"c#\",\n         \"c++\",\n         \"cobol\",\n+        \"nix\",\n       ].toSorted())\n doctype html\n html\ndiff --git a/src/views/paste.pug b/src/views/paste.pug\nindex 3014107..aca4c07 100644\n--- a/src/views/paste.pug\n+++ b/src/views/paste.pug\n@@ -15,18 +15,8 @@ html\n         | #{timeDifference(now, Date.parse(paste.createdAt))} ago ·\n         | #{paste.lang} · \n         | #{paste.code.split('\\n').length} loc · \n-        a(href=`/r/${paste.shortUrl}`) raw\n-        | \u0026nbsp;·\n-        | #{comments.length} #{pluralize(comments.length, 'comment')}\n-      pre\n-        code\n-          - var lines = paste.code.split(/\\r?\\n|\\r|\\n/g)\n-          - var tot_chars = lines.length.toString().length\n-          each line, idx in lines\n-            span.code-line\n-              span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`)\n-                | #{idx + 1}\n-              span.code-line-content #{line}\n+        a(href=`/r/${paste.shortUrl}`) raw     \n+      div.highlighted-code !{paste.highlightedCode}\n       hr\n \n       if comments.length != 0\n@@ -53,4 +43,4 @@ html\n           a(href=\"/login\") login\n           |\u0026nbsp;to post a comment\n \n-      +footer()\n+      +footer()\n\\ No newline at end of file\n-- \n2.43.0\n\n\nFrom f5924e27abdaf9aa098081623eef21fd88a2e94d Mon Sep 17 00:00:00 2001\nFrom: waveringana \u003cana@nekomimi.pet\u003e\nDate: Wed, 28 May 2025 14:09:06 -0400\nSubject: [PATCH] add copy button\n\n---\n src/public/styles.css | 37 ++++++++++++++++++++++++++++---------\n src/views/paste.pug   | 31 +++++++++++++++++++++++++++++--\n 2 files changed, 57 insertions(+), 11 deletions(-)\n\ndiff --git a/src/public/styles.css b/src/public/styles.css\nindex 61d3019..32407f3 100644\n--- a/src/public/styles.css\n+++ b/src/public/styles.css\n@@ -5,6 +5,17 @@\n     font-style: monospace;\n }\n \n+@media (prefers-color-scheme: dark) {\n+  .shiki,\n+  .shiki span {\n+    color: var(--shiki-dark) !important;\n+    background-color: var(--shiki-dark-bg) !important;\n+    font-style: var(--shiki-dark-font-style) !important;\n+    font-weight: var(--shiki-dark-font-weight) !important;\n+    text-decoration: var(--shiki-dark-text-decoration) !important;\n+  }\n+}\n+\n :root {\n   /* Light mode colors */\n   --bg-color: white;\n@@ -254,13 +265,21 @@ code .line::before {\n   color: rgba(115,138,148,.4)\n }\n \n-@media (prefers-color-scheme: dark) {\n-  .shiki,\n-  .shiki span {\n-    color: var(--shiki-dark) !important;\n-    background-color: var(--shiki-dark-bg) !important;\n-    font-style: var(--shiki-dark-font-style) !important;\n-    font-weight: var(--shiki-dark-font-weight) !important;\n-    text-decoration: var(--shiki-dark-text-decoration) !important;\n-  }\n+#copy-btn {\n+  background: none;\n+  border: none;\n+  padding: 0;\n+  color: var(--text-color-muted);\n+  cursor: pointer;\n+  text-decoration: none;\n+  font-family: inherit;\n+  font-size: inherit;\n+}\n+\n+#copy-btn:hover {\n+  text-decoration: underline;\n+}\n+\n+#copy-btn:focus {\n+  outline: 1px solid var(--accent);\n }\n\\ No newline at end of file\ndiff --git a/src/views/paste.pug b/src/views/paste.pug\nindex aca4c07..f343674 100644\n--- a/src/views/paste.pug\n+++ b/src/views/paste.pug\n@@ -15,7 +15,11 @@ html\n         | #{timeDifference(now, Date.parse(paste.createdAt))} ago ·\n         | #{paste.lang} · \n         | #{paste.code.split('\\n').length} loc · \n-        a(href=`/r/${paste.shortUrl}`) raw     \n+        a(href=`/r/${paste.shortUrl}`) raw\n+        | \u0026nbsp;·\u0026nbsp;\n+        button#copy-btn(type=\"button\" onclick=\"copyToClipboard()\" data-code=paste.code) copy\n+        | \u0026nbsp;·\n+        | #{comments.length} #{pluralize(comments.length, 'comment')}\n       div.highlighted-code !{paste.highlightedCode}\n       hr\n \n@@ -43,4 +47,27 @@ html\n           a(href=\"/login\") login\n           |\u0026nbsp;to post a comment\n \n-      +footer()\n\\ No newline at end of file\n+      +footer()\n+    script.\n+      async function copyToClipboard() {\n+        const copyBtn = document.getElementById('copy-btn');\n+        const originalText = copyBtn.textContent;\n+        \n+        try {\n+          const code = copyBtn.dataset.code;\n+          await navigator.clipboard.writeText(code);\n+          copyBtn.textContent = 'copied!';\n+          copyBtn.style.color = 'var(--accent)';\n+          \n+          setTimeout(() =\u003e {\n+            copyBtn.textContent = originalText;\n+            copyBtn.style.color = '';\n+          }, 1500);\n+        } catch (err) {\n+          console.error('Failed to copy: ', err);\n+          copyBtn.textContent = 'copy failed';\n+          setTimeout(() =\u003e {\n+            copyBtn.textContent = originalText;\n+          }, 1500);\n+        }\n+      }\n\\ No newline at end of file\n-- \n2.43.0\n\n\n",
  "pullId": 5,
  "source": {
    "branch": "main",
    "repo": "at://did:plc:ttdrpj45ibqunmfhdsb4zdwq/sh.tangled.repo/3lqasfdqkgk22"
  },
  "targetBranch": "main",
  "targetRepo": "at://did:plc:qfpnj4og54vl56wngdriaxug/sh.tangled.repo/3ljstrrobog22",
  "title": "adds syntax highlighting from shiki which server side renders, and adds a copy text button"
}