Compare commits
3 commits
65fb7cf638
...
ff91324a79
Author | SHA1 | Date | |
---|---|---|---|
ff91324a79 | |||
127e297e75 | |||
81ddec2e15 |
4 changed files with 118 additions and 13 deletions
20
README.md
20
README.md
|
@ -112,6 +112,26 @@ To automatically output the text version when visiting the site via `curl`, you
|
||||||
/blog/${1}.txt
|
/blog/${1}.txt
|
||||||
```
|
```
|
||||||
3. If you are using other web servers, you can use functions like `rewrite` or `redirect` by user agent (HTTP header) to achieve the same effect.
|
3. If you are using other web servers, you can use functions like `rewrite` or `redirect` by user agent (HTTP header) to achieve the same effect.
|
||||||
|
### Alternative Syntax Highlighting
|
||||||
|
Apart from the default shiki for highlighting, You can use [Prism](https://prismjs.com/) as code highlighting engine, see [Syntax Highlighting in Astro Docs](https://docs.astro.build/en/guides/syntax-highlighting/#add-a-prism-stylesheet) for details.
|
||||||
|
|
||||||
|
To simply add features like code copying over the default Shiki formatter, you can simply import the `src/components/CodeScript.astro` to the Layout.
|
||||||
|
|
||||||
|
You can also use [Expressive Code](https://expressive-code.com/) to get features like code copying over the syntax highlighting:
|
||||||
|
> Warning: Expressive Code will load JavaScript by default. This will not follow your `noClientJavaScript` settings.
|
||||||
|
|
||||||
|
1. Install Expressive Code:
|
||||||
|
```shell
|
||||||
|
pnpm astro add astro-expressive-code
|
||||||
|
```
|
||||||
|
You can answer all Yes to prompts as we will modify the config later.
|
||||||
|
2. Edit `astro.config.mjs`:
|
||||||
|
```diff
|
||||||
|
- integrations: [sitemap(), mdx(), partytown(), expressiveCode()],
|
||||||
|
+ integrations: [sitemap(), expressiveCode(), mdx(), partytown()],
|
||||||
|
```
|
||||||
|
3. Create `ec.config.mjs` in the project root and adjust the config to your liking. To make things easier you can use [this config file used by the author](https://raw.githubusercontent.com/BlockG-ws/gb-lab/refs/heads/master/ec.config.mjs)
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
## 👀 Want to learn more?
|
||||||
|
|
||||||
See the post [🕊](). I hope you like it. 💜
|
See the post [🕊](). I hope you like it. 💜
|
||||||
|
|
84
src/components/CodeScript.astro
Normal file
84
src/components/CodeScript.astro
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
---
|
||||||
|
// from https://www.jaluwibowo.id/blog/en/custom-syntax-highlight-and-copy-feature/
|
||||||
|
---
|
||||||
|
<script>
|
||||||
|
function renderCodeLang(preBlock) {
|
||||||
|
const lang = preBlock.dataset.language;
|
||||||
|
|
||||||
|
if (lang !== 'plaintext') {
|
||||||
|
// add lang section on top left
|
||||||
|
const langSection = document.createElement("div");
|
||||||
|
langSection.className = "code-lang";
|
||||||
|
langSection.innerHTML = lang;
|
||||||
|
|
||||||
|
preBlock.prepend(langSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const copyImg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M9 18q-.825 0-1.413-.588T7 16V4q0-.825.588-1.413T9 2h9q.825 0 1.413.588T20 4v12q0 .825-.588 1.413T18 18H9Zm0-2h9V4H9v12Zm-4 6q-.825 0-1.413-.588T3 20V7q0-.425.288-.713T4 6q.425 0 .713.288T5 7v13h10q.425 0 .713.288T16 21q0 .425-.288.713T15 22H5Zm4-6V4v12Z"/></svg>`
|
||||||
|
const checkMarkImg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m10.6 16.2l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4l4.25 4.25ZM5 21q-.825 0-1.413-.588T3 19V5q0-.825.588-1.413T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.588 1.413T19 21H5Z"/></svg>`
|
||||||
|
|
||||||
|
function renderCopyBtn(preBlock) {
|
||||||
|
const copyButton = document.createElement("button");
|
||||||
|
|
||||||
|
copyButton.className = "copy-code";
|
||||||
|
copyButton.setAttribute("aria-label", "Copy code to clipboard");
|
||||||
|
copyButton.setAttribute("title", "Copy code to clipboard");
|
||||||
|
copyButton.innerHTML = copyImg;
|
||||||
|
|
||||||
|
preBlock.appendChild(copyButton);
|
||||||
|
|
||||||
|
copyButton.addEventListener("click", async () => {
|
||||||
|
await copyCode(preBlock, copyButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function copyCode(block, button) {
|
||||||
|
const code = block.querySelector("code");
|
||||||
|
const text = code?.innerText || "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
|
||||||
|
// Visual feedback that the task is completed
|
||||||
|
button.innerHTML = checkMarkImg;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = copyImg;
|
||||||
|
}, 700);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to copy code: ", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const preBlocks = Array.from(document.querySelectorAll("pre"));
|
||||||
|
|
||||||
|
for (const preBlock of preBlocks) {
|
||||||
|
preBlock.style.position = "relative";
|
||||||
|
|
||||||
|
renderCodeLang(preBlock);
|
||||||
|
renderCopyBtn(preBlock);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style is:inline>
|
||||||
|
.code-lang {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 85%;
|
||||||
|
color: #e5e9f0;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
border-radius: 0 0 0.25rem 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-code {
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: inherit;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background-color: rgba(67, 76, 94, 0.75);
|
||||||
|
color: #d8dee9;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,13 @@
|
||||||
import { z, reference } from 'astro:content';
|
import {z, reference} from 'astro:content';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const posts = ({ image }) => z.object({
|
export const posts = ({image}) => z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
draft: z.boolean().optional().default(false),
|
draft: z.boolean().optional().default(false),
|
||||||
summary: z.string().optional(),
|
summary: z.string().optional(),
|
||||||
date: z.coerce.date(),
|
date: z.coerce.date(),
|
||||||
|
updated: z.coerce.date().optional(),
|
||||||
categories: z.union([z.array(z.string()), z.string()]).transform(val => Array.isArray(val) ? val : [val]).default(['uncategorized']),
|
categories: z.union([z.array(z.string()), z.string()]).transform(val => Array.isArray(val) ? val : [val]).default(['uncategorized']),
|
||||||
tags: z.array(z.string()).optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
cover: image().optional(),
|
cover: image().optional(),
|
||||||
|
|
|
@ -37,7 +37,7 @@ const wordcount = remarkPluginFrontmatter.wordcount;
|
||||||
const lastUpdated = remarkPluginFrontmatter.lastModified;
|
const lastUpdated = remarkPluginFrontmatter.lastModified;
|
||||||
|
|
||||||
const pubDate = new Date(entry.data.date).toISOString().split('T')[0]
|
const pubDate = new Date(entry.data.date).toISOString().split('T')[0]
|
||||||
const lastUpdatedDate = new Date(lastUpdated).toISOString().split('T')[0]
|
const lastUpdatedDate = entry.data.updated ? new Date(entry.data.updated).toISOString().split('T')[0] : new Date(lastUpdated).toISOString().split('T')[0];
|
||||||
|
|
||||||
// Get author data
|
// Get author data
|
||||||
const authorData = await Promise.all((author).map((singleAuthor) => getEntry(singleAuthor).then(authorEntry => authorEntry?.data)))
|
const authorData = await Promise.all((author).map((singleAuthor) => getEntry(singleAuthor).then(authorEntry => authorEntry?.data)))
|
||||||
|
@ -46,7 +46,7 @@ const authorInfo = authorData.includes(undefined) ? [{data: siteConfig.defaultA
|
||||||
// get featured image and use it as og:image
|
// get featured image and use it as og:image
|
||||||
// use the custom cover image if it exists, otherwise use the featured image file in the same directory
|
// use the custom cover image if it exists, otherwise use the featured image file in the same directory
|
||||||
const featuredImages = import.meta.glob(`/src/content/posts/*/featured.{avif,png,jpg,jpeg,webp}`,{import:'default',eager:true});
|
const featuredImages = import.meta.glob(`/src/content/posts/*/featured.{avif,png,jpg,jpeg,webp}`,{import:'default',eager:true});
|
||||||
const customFeaturedImage = entry.data.cover?.src
|
const customFeaturedImage = entry.data.cover
|
||||||
const matchedImage = Object.keys(featuredImages).find(path => path.includes(slug));
|
const matchedImage = Object.keys(featuredImages).find(path => path.includes(slug));
|
||||||
let matchedImage_src;
|
let matchedImage_src;
|
||||||
if (matchedImage && !customFeaturedImage) {
|
if (matchedImage && !customFeaturedImage) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue