From 576ddb9ca4592bb4a20b36c5746d4ac62eef86f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:49:43 +0000 Subject: [PATCH 1/2] Initial plan From 4068aba5b6b3dc51cc8cd6fd5452970fb12f49e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:55:54 +0000 Subject: [PATCH 2/2] feat: add //color command with native color picker Co-authored-by: da-ba <17434220+da-ba@users.noreply.github.com> --- README.md | 1 + docs/commands/README.md | 1 + docs/commands/color/README.md | 16 ++++ src/content/commands/color/command.test.ts | 58 ++++++++++++ src/content/commands/color/command.ts | 101 +++++++++++++++++++++ src/content/commands/color/index.ts | 5 + src/content/commands/index.ts | 1 + src/content/index.ts | 1 + 8 files changed, 184 insertions(+) create mode 100644 docs/commands/color/README.md create mode 100644 src/content/commands/color/command.test.ts create mode 100644 src/content/commands/color/command.ts create mode 100644 src/content/commands/color/index.ts diff --git a/README.md b/README.md index 5528c13..a7a1d1e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Try the extension without installing it. All deployments (main + PR previews): * - `//mention` for context-aware participant mentions - `//mermaid` to insert diagram templates - `//now` to insert formatted timestamps +- `//color` to pick and insert hex color codes - Uses `//` prefix to avoid conflicts with GitHub's native `/` commands - Easily extensible with new commands diff --git a/docs/commands/README.md b/docs/commands/README.md index f953946..80516ad 100644 --- a/docs/commands/README.md +++ b/docs/commands/README.md @@ -14,6 +14,7 @@ This folder contains end-user and developer documentation for each registered sl - [//mention](mention/README.md) – context-aware mention autocomplete for participants - [//mermaid](mermaid/README.md) – insert Mermaid diagram templates - [//now](now/README.md) – insert formatted date and timestamps +- [//color](color/README.md) – open a color picker and insert hex color codes ## Options diff --git a/docs/commands/color/README.md b/docs/commands/color/README.md new file mode 100644 index 0000000..bdd677b --- /dev/null +++ b/docs/commands/color/README.md @@ -0,0 +1,16 @@ +# //color Command + +Open a native color picker and insert a hex color code. + +## Usage + +1. In any GitHub comment/review textarea, type `//color` +2. Pick a color +3. Press **Insert** + +## What gets inserted + +The selected color is inserted as an uppercase hex value, for example: + +- `#EDEDED` +- `#00FF00` diff --git a/src/content/commands/color/command.test.ts b/src/content/commands/color/command.test.ts new file mode 100644 index 0000000..33a1a29 --- /dev/null +++ b/src/content/commands/color/command.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { colorCommand, DEFAULT_COLOR, normalizeHexColor } from "./command.ts" + +const { insertTextAtCursor } = vi.hoisted(() => ({ + insertTextAtCursor: vi.fn(), +})) + +vi.mock("../../picker/index.ts", () => ({ + insertTextAtCursor, + applyStyles: vi.fn(), + getCardStyles: () => ({}), + getButtonStyles: () => ({}), + getInputStyles: () => ({}), +})) + +describe("color command", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("opens setup panel in preflight", async () => { + const result = await colorCommand.preflight() + expect(result.showSetup).toBe(true) + expect(result.renderSetup).toBeDefined() + }) + + it("normalizes valid hex colors to uppercase", () => { + expect(normalizeHexColor("#abc123")).toBe("#ABC123") + }) + + it("falls back to default color for invalid values", () => { + expect(normalizeHexColor("abc")).toBe(DEFAULT_COLOR) + expect(normalizeHexColor("#abc")).toBe(DEFAULT_COLOR) + }) + + it("inserts selected color from setup panel", async () => { + const result = await colorCommand.preflight() + const body = document.createElement("div") + const onComplete = vi.fn() + + result.renderSetup?.(body, onComplete) + + const input = body.querySelector('input[type="text"]') as HTMLInputElement | null + const button = body.querySelector("button") as HTMLButtonElement | null + expect(input).toBeTruthy() + expect(button).toBeTruthy() + + if (!input || !button) { + throw new Error("setup controls not rendered") + } + + input.value = "#00ff00" + button.dispatchEvent(new MouseEvent("click", { bubbles: true })) + + expect(insertTextAtCursor).toHaveBeenCalledWith("#00FF00") + expect(onComplete).toHaveBeenCalled() + }) +}) diff --git a/src/content/commands/color/command.ts b/src/content/commands/color/command.ts new file mode 100644 index 0000000..341447a --- /dev/null +++ b/src/content/commands/color/command.ts @@ -0,0 +1,101 @@ +/** + * //color slash command implementation + * + * Opens a native color picker and inserts hex color values. + */ + +import { registerCommand, type CommandSpec } from "../registry.ts" +import { + insertTextAtCursor, + applyStyles, + getCardStyles, + getButtonStyles, + getInputStyles, +} from "../../picker/index.ts" + +const DEFAULT_COLOR = "#EDEDED" + +export function normalizeHexColor(value: string): string { + return /^#[0-9a-fA-F]{6}$/.test(value) ? value.toUpperCase() : DEFAULT_COLOR +} + +function renderColorSetupPanel(bodyEl: HTMLElement, onComplete: () => void): void { + const wrap = document.createElement("div") + applyStyles(wrap, getCardStyles()) + wrap.style.display = "flex" + wrap.style.flexDirection = "column" + wrap.style.gap = "10px" + + const title = document.createElement("div") + title.textContent = "Pick a color to insert as a hex code." + + const controls = document.createElement("div") + controls.style.display = "flex" + controls.style.gap = "10px" + controls.style.alignItems = "center" + + const colorInput = document.createElement("input") + colorInput.type = "color" + colorInput.value = DEFAULT_COLOR.toLowerCase() + colorInput.ariaLabel = "Color picker" + colorInput.style.width = "44px" + colorInput.style.height = "36px" + colorInput.style.padding = "0" + colorInput.style.border = "none" + colorInput.style.background = "transparent" + colorInput.style.cursor = "pointer" + + const hexInput = document.createElement("input") + hexInput.type = "text" + hexInput.value = DEFAULT_COLOR + hexInput.placeholder = "#RRGGBB" + hexInput.ariaLabel = "Hex color" + applyStyles(hexInput, getInputStyles()) + + colorInput.addEventListener("input", () => { + hexInput.value = normalizeHexColor(colorInput.value) + }) + + const insert = (): void => { + const normalized = normalizeHexColor(hexInput.value) + insertTextAtCursor(normalized) + onComplete() + } + + hexInput.addEventListener("keydown", (event: KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault() + insert() + } + }) + + const insertButton = document.createElement("button") + insertButton.type = "button" + insertButton.textContent = "Insert" + insertButton.ariaLabel = "Insert color" + applyStyles(insertButton, getButtonStyles()) + insertButton.addEventListener("click", insert) + + controls.append(colorInput, hexInput, insertButton) + wrap.append(title, controls) + bodyEl.appendChild(wrap) +} + +const colorCommand: CommandSpec = { + preflight: async () => ({ + showSetup: true, + message: "Pick a color to insert", + renderSetup: renderColorSetupPanel, + }), + getEmptyState: async () => ({ items: [] }), + getResults: async () => ({ items: [] }), + renderItems: () => {}, + onSelect: () => {}, +} + +registerCommand("color", colorCommand, { + icon: "🎨", + description: "Pick and insert hex color codes", +}) + +export { colorCommand, DEFAULT_COLOR } diff --git a/src/content/commands/color/index.ts b/src/content/commands/color/index.ts new file mode 100644 index 0000000..4521d01 --- /dev/null +++ b/src/content/commands/color/index.ts @@ -0,0 +1,5 @@ +/** + * Color Command Module + */ + +export * from "./command.ts" diff --git a/src/content/commands/index.ts b/src/content/commands/index.ts index dc12a66..2dad30c 100644 --- a/src/content/commands/index.ts +++ b/src/content/commands/index.ts @@ -8,5 +8,6 @@ export * from "./emoji/index.ts" export * from "./mermaid/index.ts" export * from "./mention/index.ts" export * from "./now/index.ts" +export * from "./color/index.ts" export * from "./kbd/index.ts" export * from "./link/index.ts" diff --git a/src/content/index.ts b/src/content/index.ts index 06046aa..b8f6b81 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -38,6 +38,7 @@ import "./commands/emoji/index.ts" import "./commands/mermaid/index.ts" import "./commands/mention/index.ts" import "./commands/now/index.ts" +import "./commands/color/index.ts" import "./commands/kbd/index.ts" import "./commands/link/index.ts"