mirror of
https://github.com/jimeh/commonflow.org.git
synced 2026-02-19 05:46:40 +00:00
Compare commits
35 Commits
1c25833e69
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 52cb73be47 | |||
|
|
3e689ed0b2 | ||
| 5d7068ae1d | |||
|
|
201700f27b | ||
| 540efeac89 | |||
| 0ff81cccbd | |||
| 501f89d3a0 | |||
| ba92e6c212 | |||
| 7bed32b60a | |||
|
9652714455
|
|||
|
|
b33a02991c | ||
|
|
fc393caa5b | ||
|
|
b6dfa4b787 | ||
|
|
c4bb1ae3f1 | ||
| 3a64fee42e | |||
|
e5d2604edc
|
|||
| 19a5bafeec | |||
|
|
e01d929793 | ||
| 02f2c9a887 | |||
|
|
ebe2a6be99 | ||
| 53e20b43d3 | |||
|
e85a83c2c6
|
|||
| be432b6f79 | |||
|
|
bd5963d454 | ||
| 8465487155 | |||
| 3f30bedcd3 | |||
| df2caadb70 | |||
|
ee0c0171a5
|
|||
|
|
2705ab40d0 | ||
|
|
f9f55d3a72 | ||
| 3243639e3c | |||
|
cb43e511c2
|
|||
|
0f01a1f154
|
|||
| 949cc0b5b5 | |||
|
|
42260f46b5 |
24
.github/dependabot.yml
vendored
Normal file
24
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
groups:
|
||||||
|
npm-development:
|
||||||
|
dependency-type: development
|
||||||
|
update-types:
|
||||||
|
- major
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
npm-production:
|
||||||
|
dependency-type: production
|
||||||
|
update-types:
|
||||||
|
- major
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
47
.github/workflows/update-specs.yml
vendored
Normal file
47
.github/workflows/update-specs.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Update Specs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-specs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Generate GitHub App Token
|
||||||
|
id: app-token
|
||||||
|
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.BOT_APP_ID }}
|
||||||
|
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
|
with:
|
||||||
|
node-version-file: ".node-version"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Update specs
|
||||||
|
run: npm run update-specs
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
commit-message: "chore: update spec versions"
|
||||||
|
title: "chore: update spec versions"
|
||||||
|
body: |
|
||||||
|
Automated update of spec versions from upstream repository.
|
||||||
|
|
||||||
|
This PR was created by the update-specs workflow.
|
||||||
|
branch: update-specs
|
||||||
|
delete-branch: true
|
||||||
|
add-paths: |
|
||||||
|
src/content/spec/*
|
||||||
@@ -47,7 +47,7 @@ The site is built to `dist/` and deployed to Cloudflare Workers.
|
|||||||
|
|
||||||
- **Astro 5.x** static site generator
|
- **Astro 5.x** static site generator
|
||||||
- **Tailwind CSS 4.x** for styling with dark mode support
|
- **Tailwind CSS 4.x** for styling with dark mode support
|
||||||
- **astro-icon** with Heroicons and Simple Icons for icons
|
- **astro-icon** with Heroicons, Simple Icons, and custom local SVGs (`src/icons/`)
|
||||||
- **Content Collections** for spec markdown files
|
- **Content Collections** for spec markdown files
|
||||||
- **TypeScript** throughout
|
- **TypeScript** throughout
|
||||||
- **Node.js** as JavaScript runtime (managed via mise)
|
- **Node.js** as JavaScript runtime (managed via mise)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from "astro/config";
|
import { defineConfig, fontProviders } from "astro/config";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import sitemap from "@astrojs/sitemap";
|
import sitemap from "@astrojs/sitemap";
|
||||||
import icon from "astro-icon";
|
import icon from "astro-icon";
|
||||||
@@ -10,4 +10,29 @@ export default defineConfig({
|
|||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "Bricolage Grotesque",
|
||||||
|
cssVariable: "--font-bricolage",
|
||||||
|
weights: ["200 800"],
|
||||||
|
fallbacks: ["system-ui", "sans-serif"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "DM Sans",
|
||||||
|
cssVariable: "--font-dm-sans",
|
||||||
|
weights: ["100 1000"],
|
||||||
|
fallbacks: ["system-ui", "sans-serif"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "JetBrains Mono",
|
||||||
|
cssVariable: "--font-jetbrains",
|
||||||
|
weights: ["100 800"],
|
||||||
|
fallbacks: ["SF Mono", "Consolas", "monospace"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
1121
package-lock.json
generated
1121
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -14,13 +14,10 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/sitemap": "^3.6.1",
|
"@astrojs/sitemap": "^3.7.0",
|
||||||
"@fontsource-variable/bricolage-grotesque": "^5.2.10",
|
|
||||||
"@fontsource-variable/dm-sans": "^5.2.8",
|
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
|
||||||
"@iconify-json/heroicons": "^1.2.3",
|
"@iconify-json/heroicons": "^1.2.3",
|
||||||
"@iconify-json/simple-icons": "^1.2.65",
|
"@iconify-json/simple-icons": "^1.2.69",
|
||||||
"astro": "^5.16.8",
|
"astro": "^5.17.1",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
@@ -32,18 +29,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0.9.6",
|
"@astrojs/check": "^0.9.6",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^10.0.1",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.2.0",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^10.0.0",
|
||||||
"eslint-plugin-astro": "^1.5.0",
|
"eslint-plugin-astro": "^1.6.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"svgo": "^4.0.0",
|
"svgo": "^4.0.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.52.0"
|
"typescript-eslint": "^8.56.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ interface Props {
|
|||||||
const { version, versions, svgContent } = Astro.props;
|
const { version, versions, svgContent } = Astro.props;
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ id: "about", label: "About" },
|
{ id: "about", label: "About", align: "end" },
|
||||||
{ id: "spec", label: "Read the Spec", primary: true },
|
{ id: "spec", label: "Read the Spec", primary: true },
|
||||||
{ id: "faq", label: "FAQ" },
|
{ id: "faq", label: "FAQ", align: "start" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const baseClasses = `inline-flex items-center justify-center gap-2
|
const baseClasses = `inline-flex items-center justify-center gap-2
|
||||||
@@ -29,6 +29,7 @@ const primaryClasses = `bg-sky-600 text-white
|
|||||||
const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
||||||
hover:bg-gray-100 hover:text-gray-950
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
dark:hover:bg-neutral-800 dark:hover:text-neutral-50`;
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50`;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -133,7 +134,7 @@ const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
|||||||
<!-- Navigation links -->
|
<!-- Navigation links -->
|
||||||
<nav
|
<nav
|
||||||
class="animate-fade-in-up delay-400
|
class="animate-fade-in-up delay-400
|
||||||
flex flex-nowrap items-center justify-center gap-2 sm:gap-4"
|
grid grid-cols-[1fr_auto_1fr] items-center gap-2 sm:gap-4"
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
navItems.map((item) => (
|
navItems.map((item) => (
|
||||||
@@ -142,6 +143,8 @@ const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
|||||||
class:list={[
|
class:list={[
|
||||||
baseClasses,
|
baseClasses,
|
||||||
item.primary ? primaryClasses : secondaryClasses,
|
item.primary ? primaryClasses : secondaryClasses,
|
||||||
|
item.align === "end" && "justify-self-end",
|
||||||
|
item.align === "start" && "justify-self-start",
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ import { Icon } from "astro-icon/components";
|
|||||||
>
|
>
|
||||||
<Icon name="heroicons:sun" data-theme-icon="light" class="hidden w-5 h-5" />
|
<Icon name="heroicons:sun" data-theme-icon="light" class="hidden w-5 h-5" />
|
||||||
<Icon name="heroicons:moon" data-theme-icon="dark" class="hidden w-5 h-5" />
|
<Icon name="heroicons:moon" data-theme-icon="dark" class="hidden w-5 h-5" />
|
||||||
<Icon
|
<Icon name="sun-moon" data-theme-icon="auto" class="hidden w-5 h-5" />
|
||||||
name="heroicons:computer-desktop"
|
|
||||||
data-theme-icon="auto"
|
|
||||||
class="hidden w-5 h-5"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
<!-- Tooltip -->
|
<!-- Tooltip -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
8
src/icons/sun-moon.svg
Normal file
8
src/icons/sun-moon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<!-- Circle outline -->
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
|
||||||
|
<!-- Right half filled (dark/moon side) -->
|
||||||
|
<path fill="currentColor" d="M12 8.25a3.75 3.75 0 0 1 0 7.5z"/>
|
||||||
|
<!-- Left-side sun rays (top, top-left, left, bottom-left, bottom) -->
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 3v2.25M7.227 7.227 5.636 5.636M5.25 12H3M7.227 16.773l-1.591 1.591M12 18.75V21"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
@@ -1,7 +1,5 @@
|
|||||||
---
|
---
|
||||||
import "@fontsource-variable/bricolage-grotesque";
|
import { Font } from "astro:assets";
|
||||||
import "@fontsource-variable/dm-sans";
|
|
||||||
import "@fontsource-variable/jetbrains-mono";
|
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
|
||||||
@@ -11,7 +9,9 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { title, description = config.description } = Astro.props;
|
const { title, description = config.description } = Astro.props;
|
||||||
const fullTitle = title.includes(config.title) ? title : `${title} | ${config.title}`;
|
const fullTitle = title.includes(config.title)
|
||||||
|
? title
|
||||||
|
: `${title} | ${config.title}`;
|
||||||
const canonicalUrl = Astro.url.href;
|
const canonicalUrl = Astro.url.href;
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -42,12 +42,23 @@ const canonicalUrl = Astro.url.href;
|
|||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<Font
|
||||||
|
cssVariable="--font-bricolage"
|
||||||
|
preload={[{ weight: "700" }, { weight: "800" }]}
|
||||||
|
/>
|
||||||
|
<Font cssVariable="--font-dm-sans" preload={[{ weight: "400" }]} />
|
||||||
|
<Font cssVariable="--font-jetbrains" preload={[{ weight: "400" }]} />
|
||||||
|
|
||||||
|
<!-- Slot for page-specific head content -->
|
||||||
|
<slot name="head" />
|
||||||
|
|
||||||
<!-- Prevent flash of wrong theme -->
|
<!-- Prevent flash of wrong theme -->
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
(function () {
|
(function () {
|
||||||
const mode = localStorage.getItem("theme");
|
const mode = localStorage.getItem("theme");
|
||||||
const prefersDark = window.matchMedia(
|
const prefersDark = window.matchMedia(
|
||||||
"(prefers-color-scheme: dark)",
|
"(prefers-color-scheme: dark)"
|
||||||
).matches;
|
).matches;
|
||||||
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
||||||
document.documentElement.classList.add("dark");
|
document.documentElement.classList.add("dark");
|
||||||
@@ -63,7 +74,7 @@ const canonicalUrl = Astro.url.href;
|
|||||||
document.addEventListener("astro:after-swap", () => {
|
document.addEventListener("astro:after-swap", () => {
|
||||||
const mode = localStorage.getItem("theme");
|
const mode = localStorage.getItem("theme");
|
||||||
const prefersDark = window.matchMedia(
|
const prefersDark = window.matchMedia(
|
||||||
"(prefers-color-scheme: dark)",
|
"(prefers-color-scheme: dark)"
|
||||||
).matches;
|
).matches;
|
||||||
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
||||||
document.documentElement.classList.add("dark");
|
document.documentElement.classList.add("dark");
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
|||||||
const parsed = await parseSpecContent(markdown);
|
const parsed = await parseSpecContent(markdown);
|
||||||
|
|
||||||
// Read SVG content for inline embedding
|
// Read SVG content for inline embedding
|
||||||
const svgFilePath = path.join(process.cwd(), "src/content/spec", `${version}.svg`);
|
const svgFilePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"src/content/spec",
|
||||||
|
`${version}.svg`
|
||||||
|
);
|
||||||
let svgContent: string | null = null;
|
let svgContent: string | null = null;
|
||||||
if (fs.existsSync(svgFilePath)) {
|
if (fs.existsSync(svgFilePath)) {
|
||||||
svgContent = fs.readFileSync(svgFilePath, "utf-8");
|
svgContent = fs.readFileSync(svgFilePath, "utf-8");
|
||||||
@@ -39,14 +43,19 @@ if (fs.existsSync(svgFilePath)) {
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={spec.data.title}>
|
<BaseLayout title={spec.data.title}>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<link
|
||||||
|
rel="alternate"
|
||||||
|
type="text/markdown"
|
||||||
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
|
title="Raw Markdown"
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
<Header version={version} versions={versions} />
|
<Header version={version} versions={versions} />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<Hero
|
<Hero version={version} versions={versions} svgContent={svgContent} />
|
||||||
version={version}
|
|
||||||
versions={versions}
|
|
||||||
svgContent={svgContent}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AboutSection
|
<AboutSection
|
||||||
introduction={parsed.introduction}
|
introduction={parsed.introduction}
|
||||||
|
|||||||
25
src/pages/llms.txt.ts
Normal file
25
src/pages/llms.txt.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { getVersionInfo } from "../utils/versions";
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
const { currentVersion } = await getVersionInfo();
|
||||||
|
|
||||||
|
const content = `# Common-Flow
|
||||||
|
|
||||||
|
> A Git workflow specification combining GitHub Flow with versioned releases.
|
||||||
|
|
||||||
|
Common-Flow is a sensible git workflow based on GitHub Flow, with the addition
|
||||||
|
of versioned releases, optional release branches, and without the requirement
|
||||||
|
to deploy to production all the time.
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- [Git Common-Flow Specification](/spec/git-common-flow-v${currentVersion}.md): The complete Git Common-Flow v${currentVersion} specification in Markdown format
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Response(content, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -116,6 +116,15 @@ const previewHtml = await unified()
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={`${spec.data.title} - Markdown`}>
|
<BaseLayout title={`${spec.data.title} - Markdown`}>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<link
|
||||||
|
rel="alternate"
|
||||||
|
type="text/markdown"
|
||||||
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
|
title="Raw Markdown"
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header
|
<header
|
||||||
class="sticky top-0 z-50 border-b
|
class="sticky top-0 z-50 border-b
|
||||||
@@ -201,31 +210,31 @@ const previewHtml = await unified()
|
|||||||
<!-- Copy and Download buttons -->
|
<!-- Copy and Download buttons -->
|
||||||
<div class="flex items-center gap-3 order-1 sm:order-3">
|
<div class="flex items-center gap-3 order-1 sm:order-3">
|
||||||
<!-- Copy button -->
|
<!-- Copy button -->
|
||||||
<div class="relative">
|
<button
|
||||||
<button
|
id="copy-btn"
|
||||||
id="copy-btn"
|
type="button"
|
||||||
type="button"
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
font-medium rounded-lg transition-colors cursor-pointer
|
||||||
font-medium rounded-lg transition-colors cursor-pointer
|
text-gray-600 dark:text-neutral-400
|
||||||
text-gray-600 dark:text-neutral-400
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
hover:bg-gray-100 hover:text-gray-950
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
>
|
||||||
>
|
<Icon name="heroicons:clipboard-document" class="w-4 h-4" />
|
||||||
<Icon name="heroicons:clipboard-document" class="w-4 h-4" />
|
<span data-copy-text>Copy</span>
|
||||||
<span data-copy-text>Copy</span>
|
</button>
|
||||||
</button>
|
|
||||||
<!-- Mobile tooltip -->
|
<!-- Raw button -->
|
||||||
<div
|
<a
|
||||||
id="copy-tooltip"
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
class="sm:hidden absolute left-1/2 -translate-x-1/2 top-full
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
mt-2 px-2 py-1 text-xs font-medium whitespace-nowrap
|
font-medium rounded-lg transition-colors
|
||||||
rounded-md shadow-sm bg-gray-900 text-white
|
text-gray-600 dark:text-neutral-400
|
||||||
dark:bg-white dark:text-gray-900 opacity-0
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
pointer-events-none transition-opacity duration-200"
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
>
|
>
|
||||||
Copied!
|
<Icon name="heroicons:document-text" class="w-4 h-4" />
|
||||||
</div>
|
<span>Raw</span>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
<!-- Download button -->
|
<!-- Download button -->
|
||||||
<button
|
<button
|
||||||
@@ -368,7 +377,6 @@ const previewHtml = await unified()
|
|||||||
const downloadBtn = document.getElementById("download-btn");
|
const downloadBtn = document.getElementById("download-btn");
|
||||||
const rawContent = document.getElementById("markdown-raw");
|
const rawContent = document.getElementById("markdown-raw");
|
||||||
const copyText = copyBtn?.querySelector("[data-copy-text]");
|
const copyText = copyBtn?.querySelector("[data-copy-text]");
|
||||||
const copyTooltip = document.getElementById("copy-tooltip");
|
|
||||||
|
|
||||||
const toggleContainer = document.getElementById("toggle-container");
|
const toggleContainer = document.getElementById("toggle-container");
|
||||||
const toggleCode = document.getElementById("toggle-code");
|
const toggleCode = document.getElementById("toggle-code");
|
||||||
@@ -446,9 +454,24 @@ const previewHtml = await unified()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize indicator position on load
|
// Initialize indicator position after layout is complete
|
||||||
if (togglePreview) {
|
// Use double requestAnimationFrame to ensure layout/paint is finished,
|
||||||
updateIndicator(togglePreview);
|
// which fixes sizing issues on iOS Safari initial page load
|
||||||
|
function initializeIndicator() {
|
||||||
|
if (togglePreview) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
updateIndicator(togglePreview);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for fonts to load before measuring, then initialize
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(initializeIndicator);
|
||||||
|
} else {
|
||||||
|
initializeIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle event listeners
|
// Toggle event listeners
|
||||||
@@ -456,22 +479,12 @@ const previewHtml = await unified()
|
|||||||
togglePreview?.addEventListener("click", () => setActiveToggle(false));
|
togglePreview?.addEventListener("click", () => setActiveToggle(false));
|
||||||
|
|
||||||
function showCopiedFeedback() {
|
function showCopiedFeedback() {
|
||||||
// Desktop: change button text
|
|
||||||
if (copyText) {
|
if (copyText) {
|
||||||
copyText.textContent = "Copied!";
|
copyText.textContent = "Copied!";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyText.textContent = "Copy";
|
copyText.textContent = "Copy";
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
// Mobile: show tooltip
|
|
||||||
if (copyTooltip) {
|
|
||||||
copyTooltip.classList.remove("opacity-0");
|
|
||||||
copyTooltip.classList.add("opacity-100");
|
|
||||||
setTimeout(() => {
|
|
||||||
copyTooltip.classList.remove("opacity-100");
|
|
||||||
copyTooltip.classList.add("opacity-0");
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy button
|
// Copy button
|
||||||
|
|||||||
30
src/pages/spec/git-common-flow-v[version].md.ts
Normal file
30
src/pages/spec/git-common-flow-v[version].md.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { APIRoute, GetStaticPaths } from "astro";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
return specs.map((spec) => ({
|
||||||
|
params: { version: spec.data.version },
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ params }) => {
|
||||||
|
const version = params.version;
|
||||||
|
const filePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"src/content/spec",
|
||||||
|
`${version}.md`
|
||||||
|
);
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
||||||
|
const filename = `git-common-flow-v${version}.md`;
|
||||||
|
|
||||||
|
return new Response(markdown, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/markdown; charset=utf-8",
|
||||||
|
"Content-Disposition": `inline; filename="${filename}"`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
--color-accent: theme(colors.sky.500);
|
--color-accent: theme(colors.sky.500);
|
||||||
--color-accent-light: theme(colors.sky.400);
|
--color-accent-light: theme(colors.sky.400);
|
||||||
|
|
||||||
/* Fonts - self-hosted via fontsource */
|
/* Fonts - via Astro experimental font API (fontsource provider) */
|
||||||
--font-display: "Bricolage Grotesque Variable", system-ui, sans-serif;
|
--font-display: var(--font-bricolage);
|
||||||
--font-sans: "DM Sans Variable", system-ui, sans-serif;
|
--font-sans: var(--font-dm-sans);
|
||||||
--font-mono: "JetBrains Mono Variable", "SF Mono", Consolas, monospace;
|
--font-mono: var(--font-jetbrains);
|
||||||
|
|
||||||
/* Sizing */
|
/* Sizing */
|
||||||
--header-height: 4rem;
|
--header-height: 4rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user