diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 70765db..5763e77 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,9 +2,9 @@ name: Lint on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] permissions: contents: read @@ -14,12 +14,19 @@ jobs: golangci-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - - run: script/vendor-onnxruntime - - run: source script/env - - uses: golangci/golangci-lint-action@v9 - with: - version: v2.9.0 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - run: script/vendor-onnxruntime + - run: source script/env + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.9.0 + biome: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: biomejs/setup-biome@v2 + with: + version: v2.3.15 diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..dab12ec --- /dev/null +++ b/biome.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": ["./web/static/css/*.css", "./web/static/js/*.js"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/script/lint b/script/lint index f56367a..1614bc4 100755 --- a/script/lint +++ b/script/lint @@ -1,14 +1,9 @@ #!/bin/sh REPO_ROOT="$(git rev-parse --show-toplevel)" -source "${REPO_ROOT}/script/env" -BINDIR="${REPO_ROOT}/bin" -BINARY=$BINDIR/golangci-lint -GOLANGCI_LINT_VERSION=v2.9.0 +echo "linting go code..." +"${REPO_ROOT}/script/lint-go" -if [ ! -f "$BINARY" ]; then - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s "$GOLANGCI_LINT_VERSION" -fi - -$BINARY run +echo "linting web code..." +"${REPO_ROOT}/script/lint-web" diff --git a/script/lint-go b/script/lint-go new file mode 100755 index 0000000..fd3944c --- /dev/null +++ b/script/lint-go @@ -0,0 +1,15 @@ +#!/bin/sh + +REPO_ROOT="$(git rev-parse --show-toplevel)" +source "${REPO_ROOT}/script/env" + +BINDIR="${REPO_ROOT}/bin" + +GOLANGCI_LINT_BINARY=$BINDIR/golangci-lint +GOLANGCI_LINT_VERSION=v2.9.0 + +if [ ! -f "$GOLANGCI_LINT_BINARY" ]; then + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s "$GOLANGCI_LINT_VERSION" +fi + +$GOLANGCI_LINT_BINARY run diff --git a/script/lint-web b/script/lint-web new file mode 100755 index 0000000..d2c43c6 --- /dev/null +++ b/script/lint-web @@ -0,0 +1,22 @@ +#!/bin/sh + +REPO_ROOT="$(git rev-parse --show-toplevel)" + +BINDIR="${REPO_ROOT}/bin" + +BIOME_BINARY=$BINDIR/biome +BIOME_VERSION="@biomejs/biome@2.3.15" + +if [ ! -f "$BIOME_BINARY" ]; then + case "$(uname -s)-$(uname -m)" in + Darwin-arm64) BIOME_TARGET="biome-darwin-arm64" ;; + Darwin-x86_64) BIOME_TARGET="biome-darwin-x64" ;; + Linux-x86_64) BIOME_TARGET="biome-linux-x64" ;; + Linux-aarch64) BIOME_TARGET="biome-linux-arm64" ;; + *) echo "Unsupported platform: $(uname -s)-$(uname -m)"; exit 1 ;; + esac + curl -sSfL "https://github.com/biomejs/biome/releases/download/${BIOME_VERSION}/${BIOME_TARGET}" -o "$BIOME_BINARY" + chmod +x "$BIOME_BINARY" +fi + +$BIOME_BINARY check diff --git a/web/static/css/code.css b/web/static/css/code.css index 0118ba5..d10a912 100644 --- a/web/static/css/code.css +++ b/web/static/css/code.css @@ -1,57 +1,57 @@ .code { - margin: 1.5rem 1rem; + margin: 1.5rem 1rem; } .code .chroma .line { - display: flex; - line-height: 1.5rem; + display: flex; + line-height: 1.5rem; } .code .chroma .hl { - background-color: unset; + background-color: unset; } .code .chroma .ln:target { - background-color: unset; + background-color: unset; } .code .chroma .hl .cl { - position: relative; - flex: 1; + position: relative; + flex: 1; } .code .chroma .ln { - margin-right: 0.4rem; - padding: 0 0.4rem 0 0.8rem; + margin-right: 0.4rem; + padding: 0 0.4rem 0 0.8rem; } .code .chroma .ln + .cl { - padding-left: 0.75rem; + padding-left: 0.75rem; } .code .chroma .hl .cl:before { - content: ""; - display: block; - position: absolute; - top: 0; - left: 0; - width: 2px; - height: 100%; - background-color: var(--color-primary); + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + width: 2px; + height: 100%; + background-color: var(--color-primary); } .code .chroma .hl .cl:after { - content: ""; - display: block; - position: absolute; - top: 0; - right: 0; - width: 100%; - height: 100%; - background-color: var(--color-primary); - opacity: 0.05; + content: ""; + display: block; + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + background-color: var(--color-primary); + opacity: 0.05; } .code .chroma .hl .lnlinks { - color: var(--color-primary); + color: var(--color-primary); } diff --git a/web/static/css/index.css b/web/static/css/index.css index 9050ed6..c825654 100644 --- a/web/static/css/index.css +++ b/web/static/css/index.css @@ -1,354 +1,354 @@ @import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;600&family=Geist+Mono:wght@400;600&display=swap"); @font-face { - font-family: "GeistPixel-Line"; - src: url("/assets/fonts/GeistPixel-Line.woff2") format("woff2"); - font-display: swap; + font-family: "GeistPixel-Line"; + src: url("/assets/fonts/GeistPixel-Line.woff2") format("woff2"); + font-display: swap; } @font-face { - font-family: "GeistPixel-Square"; - src: url("/assets/fonts/GeistPixel-Square.woff2") format("woff2"); - font-display: swap; + font-family: "GeistPixel-Square"; + src: url("/assets/fonts/GeistPixel-Square.woff2") format("woff2"); + font-display: swap; } :root { - --max-width: 1200px; - --font-sans: - "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, - Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; - --font-mono: - "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; - --border: 1px solid var(--color-border); - --border-radius: 0; - - --color-surface-0: hsl(220 13% 8%); - --color-surface-1: hsl(220 13% 10%); - --color-surface-2: hsl(220 13% 14%); - --color-border: hsl(220 13% 18%); - - --color-gray: hsl(220 5% 55%); - --color-blue: hsl(212 100% 70%); - --color-red: hsl(356 100% 72%); - --color-amber: hsl(42 92% 54%); - --color-green: hsl(134 48% 62%); - --color-teal: hsl(171 85% 45%); - --color-purple: hsl(278 76% 74%); - --color-pink: hsl(344 88% 71%); - --color-white: hsl(0 0% 100%); - --color-primary: var(--color-blue); + --max-width: 1200px; + --font-sans: + "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --font-mono: + "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --border: 1px solid var(--color-border); + --border-radius: 0; + + --color-surface-0: hsl(220 13% 8%); + --color-surface-1: hsl(220 13% 10%); + --color-surface-2: hsl(220 13% 14%); + --color-border: hsl(220 13% 18%); + + --color-gray: hsl(220 5% 55%); + --color-blue: hsl(212 100% 70%); + --color-red: hsl(356 100% 72%); + --color-amber: hsl(42 92% 54%); + --color-green: hsl(134 48% 62%); + --color-teal: hsl(171 85% 45%); + --color-purple: hsl(278 76% 74%); + --color-pink: hsl(344 88% 71%); + --color-white: hsl(0 0% 100%); + --color-primary: var(--color-blue); } ::selection { - background: color-mix(in srgb, var(--color-primary) 40%, transparent); + background: color-mix(in srgb, var(--color-primary) 40%, transparent); } :focus-visible { - outline: 2px dotted var(--color-primary); - outline-offset: 3px; + outline: 2px dotted var(--color-primary); + outline-offset: 3px; } * { - color-scheme: dark; - box-sizing: border-box; + color-scheme: dark; + box-sizing: border-box; } *[data-hide] { - display: none; + display: none; } html { - text-size-adjust: none; - -webkit-text-size-adjust: none; - font-size: 14px; + text-size-adjust: none; + -webkit-text-size-adjust: none; + font-size: 14px; } body { - font-family: var(--font-sans); - background-color: var(--color-surface-0); - color: var(--color-gray-10); - margin: 0; + font-family: var(--font-sans); + background-color: var(--color-surface-0); + color: var(--color-white); + margin: 0; } code { - font-family: var(--font-mono); + font-family: var(--font-mono); } span[role="img"] { - font-family: serif; + font-family: serif; } a { - color: var(--color-primary); - text-decoration: none; + color: var(--color-primary); + text-decoration: none; } a:hover { - text-decoration: underline; - text-underline-offset: 0.25rem; + text-decoration: underline; + text-underline-offset: 0.25rem; } svg.lucide { - width: 1rem; - height: 1rem; + width: 1rem; + height: 1rem; } .container { - max-width: var(--max-width); - margin: 0 auto; - padding: 0 2rem; + max-width: var(--max-width); + margin: 0 auto; + padding: 0 2rem; } .header { - display: flex; - align-items: center; - gap: 1.25rem; + display: flex; + align-items: center; + gap: 1.25rem; } .header .title { - font-size: 5rem; - position: relative; - font-weight: normal; - letter-spacing: 0.2rem; - font-family: "GeistPixel-Square"; + font-size: 5rem; + position: relative; + font-weight: normal; + letter-spacing: 0.2rem; + font-family: "GeistPixel-Square", monospace; } .header .logo { - position: relative; - display: inline-block; - margin-top: 1rem; - isolation: isolate; + position: relative; + display: inline-block; + margin-top: 1rem; + isolation: isolate; } .header .logo img { - display: block; - height: 3rem; - image-rendering: pixelated; - position: relative; - mix-blend-mode: lighten; + display: block; + height: 3rem; + image-rendering: pixelated; + position: relative; + mix-blend-mode: lighten; } .header .logo::before { - content: ""; - position: absolute; - inset: 0; - background-color: var(--color-primary); - -webkit-mask-image: url("/assets/img/logo.png"); - mask-image: url("/assets/img/logo.png"); - -webkit-mask-size: contain; - mask-size: contain; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - image-rendering: pixelated; + content: ""; + position: absolute; + inset: 0; + background-color: var(--color-primary); + -webkit-mask-image: url("/assets/img/logo.png"); + mask-image: url("/assets/img/logo.png"); + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + image-rendering: pixelated; } .header .title::after { - content: "snips"; - position: absolute; - top: 0; - left: -0.4rem; - opacity: 0.4; - font-family: "GeistPixel-Line"; - z-index: -1; + content: "snips"; + position: absolute; + top: 0; + left: -0.4rem; + opacity: 0.4; + font-family: "GeistPixel-Line", monospace; + z-index: -1; } .header a { - color: var(--color-gray-10); + color: var(--color-gray-10); } .stripes { - display: block; - width: 100%; - height: 1rem; - background-image: repeating-linear-gradient( - -45deg, - var(--color-primary) 0 2px, - transparent 2px 8px - ); + display: block; + width: 100%; + height: 1rem; + background-image: repeating-linear-gradient( + -45deg, + var(--color-primary) 0 2px, + transparent 2px 8px + ); } .file-header { - position: sticky; - top: 0; - display: flex; - align-items: center; - justify-content: space-around; - flex-wrap: wrap; - white-space: nowrap; - font-family: var(--font-mono); - border: var(--border); - border-radius: var(--border-radius); - z-index: 999; - background-color: var(--color-surface-1); - overflow-x: scroll; + position: sticky; + top: 0; + display: flex; + align-items: center; + justify-content: space-around; + flex-wrap: wrap; + white-space: nowrap; + font-family: var(--font-mono); + border: var(--border); + border-radius: var(--border-radius); + z-index: 999; + background-color: var(--color-surface-1); + overflow-x: scroll; } .file-header .file-details { - padding: 1rem; - color: var(--color-gray); - flex: 1; - display: flex; - gap: 1.2rem; - align-items: center; + padding: 1rem; + color: var(--color-gray); + flex: 1; + display: flex; + gap: 1.2rem; + align-items: center; } .file-header .file-details .file-detail { - display: flex; - align-items: center; - gap: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; } .file-header .file-actions { - display: flex; - padding: 1rem; - gap: 1rem; - margin-right: 0.5rem; + display: flex; + padding: 1rem; + gap: 1rem; + margin-right: 0.5rem; } .file-header .file-actions .file-action { - background-color: unset; - border: none; - font-family: var(--font-mono); - font-size: 0.875rem; - color: var(--color-gray); - padding: 0; - transition: color 0.15s ease; + background-color: unset; + border: none; + font-family: var(--font-mono); + font-size: 0.875rem; + color: var(--color-gray); + padding: 0; + transition: color 0.15s ease; } .file-header .file-actions .file-action:hover { - color: var(--color-primary); - text-decoration: none; - cursor: pointer; + color: var(--color-primary); + text-decoration: none; + cursor: pointer; } .file-header .file-actions .file-action kbd { - font-family: var(--font-mono); - color: var(--color-white); - background-color: var(--color-surface-0); - border: var(--border); - margin-right: 0.5rem; - font-size: 0.75rem; - padding: 0.15rem 0.35rem; + font-family: var(--font-mono); + color: var(--color-white); + background-color: var(--color-surface-0); + border: var(--border); + margin-right: 0.5rem; + font-size: 0.75rem; + padding: 0.15rem 0.35rem; } .file-header .file-actions .file-action:hover kbd { - color: var(--color-primary); + color: var(--color-primary); } .file-content { - overflow-x: auto; - border: var(--border); - border-top: none; + overflow-x: auto; + border: var(--border); + border-top: none; } .file-content * { - scroll-margin-top: 3.5rem; /* header offset */ + scroll-margin-top: 3.5rem; /* header offset */ } .file-footer { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - gap: 1rem; - padding: 1rem; - font-family: var(--font-mono); - background-color: var(--color-surface-1); - border: var(--border); - margin-bottom: 2rem; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; + font-family: var(--font-mono); + background-color: var(--color-surface-1); + border: var(--border); + margin-bottom: 2rem; } .footer-stripes-container { - margin-top: 1.5rem; - margin-bottom: 1.5rem; - display: flex; - align-items: center; - gap: 0.75rem; + margin-top: 1.5rem; + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.75rem; } .footer-stripes { - flex: 1; - display: block; - height: 1rem; - background-image: repeating-linear-gradient( - -45deg, - var(--color-border) 0 2px, - transparent 2px 8px - ); + flex: 1; + display: block; + height: 1rem; + background-image: repeating-linear-gradient( + -45deg, + var(--color-border) 0 2px, + transparent 2px 8px + ); } .color-picker { - display: flex; - gap: 4px; - flex-shrink: 0; + display: flex; + gap: 4px; + flex-shrink: 0; } .color-swatch { - width: 1rem; - height: 1rem; - padding: 0; - border: 2px solid transparent; - cursor: pointer; - box-sizing: border-box; + width: 1rem; + height: 1rem; + padding: 0; + border: 2px solid transparent; + cursor: pointer; + box-sizing: border-box; } .color-swatch.active { - border-color: var(--color-white); + border-color: var(--color-white); } .danger { - color: var(--color-red); + color: var(--color-red); } .muted { - color: var(--color-gray); + color: var(--color-gray); } .text-sm { - font-size: 0.875rem; - line-height: 1.25rem; + font-size: 0.875rem; + line-height: 1.25rem; } @media (max-width: 768px) { - .container { - padding: 0 0.5rem; - padding-bottom: 3.5rem; - } - - .header { - gap: 0.75rem; - } - - .header .title { - font-size: 3.5rem; - letter-spacing: 0.1rem; - } - - .header .title::after { - left: -0.3rem; - } - - .header .logo { - margin-top: 0.5rem; - } - - .header .logo img { - height: 2.25rem; - } - - .file-header { - overflow-x: visible; - } - - .file-header .file-actions { - position: fixed; - bottom: 0; - left: 0; - right: 0; - margin: 0; - justify-content: center; - background-color: var(--color-surface-1); - border-top: var(--border); - z-index: 999; - } + .container { + padding: 0 0.5rem; + padding-bottom: 3.5rem; + } + + .header { + gap: 0.75rem; + } + + .header .title { + font-size: 3.5rem; + letter-spacing: 0.1rem; + } + + .header .title::after { + left: -0.3rem; + } + + .header .logo { + margin-top: 0.5rem; + } + + .header .logo img { + height: 2.25rem; + } + + .file-header { + overflow-x: visible; + } + + .file-header .file-actions { + position: fixed; + bottom: 0; + left: 0; + right: 0; + margin: 0; + justify-content: center; + background-color: var(--color-surface-1); + border-top: var(--border); + z-index: 999; + } } diff --git a/web/static/css/markdown.css b/web/static/css/markdown.css index 51d1d43..2e2821e 100644 --- a/web/static/css/markdown.css +++ b/web/static/css/markdown.css @@ -1,13 +1,13 @@ .markdown { - font-family: var(--font-sans); - box-sizing: border-box; - padding: 2rem; - font-size: 16px; - line-height: 1.6; + font-family: var(--font-sans); + box-sizing: border-box; + padding: 2rem; + font-size: 16px; + line-height: 1.6; } .markdown > :first-child { - margin-top: unset !important; + margin-top: unset; } .markdown h1, @@ -16,10 +16,10 @@ .markdown h4, .markdown h5, .markdown h6 { - font-weight: 600; - padding-bottom: 0.3rem; - margin-top: 1.5rem; - margin-bottom: 0.75rem; + font-weight: 600; + padding-bottom: 0.3rem; + margin-top: 1.5rem; + margin-bottom: 0.75rem; } .markdown h1, @@ -28,76 +28,76 @@ .markdown h4, .markdown h5, .markdown h6 { - font-weight: 600; - padding-bottom: 0.3rem; - margin-top: 1.5rem; - margin-bottom: 0.75rem; + font-weight: 600; + padding-bottom: 0.3rem; + margin-top: 1.5rem; + margin-bottom: 0.75rem; } .markdown h1, .markdown h2 { - border-bottom: var(--border); + border-bottom: var(--border); } .markdown table { - display: block; - overflow: auto; - width: max-content; - max-width: 100%; - border-spacing: 0; - border-collapse: collapse; + display: block; + overflow: auto; + width: max-content; + max-width: 100%; + border-spacing: 0; + border-collapse: collapse; } .markdown table th { - background: var(--color-surface-1); - font-weight: 600; + background: var(--color-surface-1); + font-weight: 600; } .markdown tr:nth-child(2n) { - background: var(--color-surface-1); + background: var(--color-surface-1); } .markdown table th, .markdown table td { - padding: 0.5rem 1rem; - border: var(--border); + padding: 0.5rem 1rem; + border: var(--border); } .markdown blockquote { - margin: unset !important; - padding: 0rem 1rem; - border-left: 0.2rem solid var(--color-border); - color: var(--color-gray); + margin: unset; + padding: 0rem 1rem; + border-left: 0.2rem solid var(--color-border); + color: var(--color-gray); } .markdown code { - background-color: var(--color-surface-2); + background-color: var(--color-surface-2); } .markdown pre { - background-color: var(--color-surface-1); + background-color: var(--color-surface-1); } .markdown pre code { - background: unset; + background: unset; } .markdown pre { - margin: 0.5rem 0; - padding: 1rem; - overflow-x: auto; + margin: 0.5rem 0; + padding: 1rem; + overflow-x: auto; } .markdown pre code { - padding: unset; + padding: unset; } .markdown code { - margin: unset !important; - padding: 0.2em 0.4em; - font-size: 85%; + margin: unset; + padding: 0.2em 0.4em; + font-size: 85%; } .markdown hr { - border: var(--border); + border: var(--border); } diff --git a/web/static/js/snips.js b/web/static/js/snips.js index 137ffcd..e160d6a 100644 --- a/web/static/js/snips.js +++ b/web/static/js/snips.js @@ -1,12 +1,12 @@ import { createIcons, - Terminal, FileCode, + FileText, + Folder, HardDrive, - SquarePen, HatGlasses, - Folder, - FileText, + SquarePen, + Terminal, } from "lucide"; // getSelectedLines will return the lines specified in the hash. @@ -15,8 +15,8 @@ const getSelectedLines = () => { return location.hash .slice(1) .split("-") - .map((n) => parseInt(n.slice(1))) - .filter((e) => !isNaN(e)) + .map((n) => parseInt(n.slice(1), 10)) + .filter((e) => !Number.isNaN(e)) .sort((a, b) => a - b); }; @@ -60,13 +60,14 @@ const watchForShiftClick = () => { event.preventDefault(); - const lineNum = parseInt(el.href.split("#")[1].slice(1)); - if (isNaN(lineNum)) return; + const lineNum = parseInt(el.href.split("#")[1].slice(1), 10); + if (Number.isNaN(lineNum)) return; const lines = getSelectedLines(); switch (lines.length) { case 0: location.hash = `#L${lineNum}`; + break; case 1: if (lineNum < lines[0]) { lines.unshift(lineNum); @@ -74,6 +75,7 @@ const watchForShiftClick = () => { lines.push(lineNum); } location.hash = `#L${lines[0]}-L${lines[1]}`; + break; case 2: if (lineNum < lines[0]) { lines[1] = lines[0]; @@ -84,6 +86,7 @@ const watchForShiftClick = () => { lines[1] = lineNum; } location.hash = `#L${lines[0]}-L${lines[1]}`; + break; default: return; } @@ -217,7 +220,9 @@ const initColorPicker = () => { swatch.addEventListener("click", () => { const color = swatch.dataset.color; localStorage.setItem("color-primary", color); - swatches.forEach((s) => s.classList.toggle("active", s === swatch)); + swatches.forEach((s) => { + s.classList.toggle("active", s === swatch); + }); applyColor(color); }); });