Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion browser-extension/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": [".*", "src/**", "tests/**", "!src/overtype", "!src/playgrounds"]
"includes": [".*", "*.config.ts", "src/**", "tests/**", "!src/overtype"]
},
"formatter": {
"enabled": true,
Expand Down
5 changes: 2 additions & 3 deletions browser-extension/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
import { defineConfig } from '@playwright/test'

export default defineConfig({
reporter: [['html', { open: 'never' }]],
testDir: 'tests/e2e',
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
reporter: [['html', { open: 'never' }]],
})

6 changes: 5 additions & 1 deletion browser-extension/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ const MODES = ['PROD', 'PLAYGROUNDS_PR'] as const

export type ModeType = (typeof MODES)[number]

const LOG_LEVELS = ['DEBUG', 'INFO', 'WARN', 'ERROR'] as const

export type LogLevel = (typeof LOG_LEVELS)[number]

export const CONFIG = {
ADDED_OVERTYPE_CLASS: 'gitcasso-overtype',
DEBUG: true, // enabled debug logging
EXTENSION_NAME: 'gitcasso', // decorates logs
LOG_LEVEL: 'INFO' satisfies LogLevel,
MODE: 'PROD' satisfies ModeType,
} as const
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import OverType, { type OverTypeInstance } from 'overtype'
import type { CommentEnhancer, CommentSpot } from '../../enhancer'
import { logger } from '../../logger'
import { modifyDOM } from '../modifyDOM'
import { githubHighlighter } from './githubHighlighter'

interface GitHubIssueNewCommentSpot extends CommentSpot {
type: 'GH_ISSUE_NEW_COMMENT'
domain: string
slug: string // owner/repo
}

export class GitHubIssueNewCommentEnhancer implements CommentEnhancer<GitHubIssueNewCommentSpot> {
forSpotTypes(): string[] {
return ['GH_ISSUE_NEW_COMMENT']
}

tryToEnhance(_textarea: HTMLTextAreaElement): GitHubIssueNewCommentSpot | null {
if (document.querySelector('meta[name="hostname"]')?.getAttribute('content') !== 'github.com') {
return null
}

// Parse GitHub URL structure: /owner/repo/issues/123 or /owner/repo/pull/456
logger.debug(`${this.constructor.name} examing url`, window.location.pathname)

const match = window.location.pathname.match(/^\/([^/]+)\/([^/]+)(?:\/issues\/new)/)
logger.debug(`${this.constructor.name} found match`, window.location.pathname)

if (!match) return null
const [, owner, repo] = match
const slug = `${owner}/${repo}`
const unique_key = `github.com:${slug}:new`
return {
domain: 'github.com',
slug,
type: 'GH_ISSUE_NEW_COMMENT',
unique_key,
}
}

prepareForFirstEnhancement(): void {
OverType.setCodeHighlighter(githubHighlighter)
}

enhance(textArea: HTMLTextAreaElement, _spot: GitHubIssueNewCommentSpot): OverTypeInstance {
const overtypeContainer = modifyDOM(textArea)
return new OverType(overtypeContainer, {
autoResize: true,
minHeight: '400px',
padding: 'var(--base-size-16)',
placeholder: 'Type your description here...',
})[0]!
}

tableTitle(spot: GitHubIssueNewCommentSpot): string {
const { slug } = spot
return `${slug} New Issue`
}

tableIcon(_: GitHubIssueNewCommentSpot): string {
return '🔄' // PR icon TODO: icon urls in /public
}

buildUrl(spot: GitHubIssueNewCommentSpot): string {
return `https://${spot.domain}/${spot.slug}/issue/new`
}
}
72 changes: 72 additions & 0 deletions browser-extension/src/lib/enhancers/github/githubPRNewComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import OverType, { type OverTypeInstance } from 'overtype'
import type { CommentEnhancer, CommentSpot } from '../../enhancer'
import { logger } from '../../logger'
import { modifyDOM } from '../modifyDOM'
import { githubHighlighter } from './githubHighlighter'

interface GitHubPRNewCommentSpot extends CommentSpot {
type: 'GH_PR_NEW_COMMENT'
domain: string
slug: string // owner/repo/base-branch/compare-branch
}

export class GitHubPRNewCommentEnhancer implements CommentEnhancer<GitHubPRNewCommentSpot> {
forSpotTypes(): string[] {
return ['GH_PR_NEW_COMMENT']
}

tryToEnhance(_textarea: HTMLTextAreaElement): GitHubPRNewCommentSpot | null {
if (document.querySelector('meta[name="hostname"]')?.getAttribute('content') !== 'github.com') {
return null
}

// /owner/repo/compare/feature/more-enhancers?expand=1
// or /owner/repo/compare/feat/issue-static-and-dynamic...feature/more-enhancers?expand=1
logger.info(`${this.constructor.name} examing url`, window.location.pathname)

const match = window.location.pathname.match(
/^\/([^/]+)\/([^/]+)\/compare\/(?:([^.?]+)\.\.\.)?([^?]+)/,
)
logger.info(`${this.constructor.name} found match`, window.location.pathname, match)

if (!match) return null
const [, owner, repo, baseBranch, compareBranch] = match
const slug = baseBranch
? `${owner}/${repo}/${baseBranch}...${compareBranch}`
: `${owner}/${repo}/${compareBranch}`
const unique_key = `github.com:${slug}`
return {
domain: 'github.com',
slug,
type: 'GH_PR_NEW_COMMENT',
unique_key,
}
}

prepareForFirstEnhancement(): void {
OverType.setCodeHighlighter(githubHighlighter)
}

enhance(textArea: HTMLTextAreaElement, _spot: GitHubPRNewCommentSpot): OverTypeInstance {
const overtypeContainer = modifyDOM(textArea)
return new OverType(overtypeContainer, {
autoResize: true,
minHeight: '250px',
padding: 'var(--base-size-16)',
placeholder: 'Type your description here...',
})[0]!
}

tableTitle(spot: GitHubPRNewCommentSpot): string {
const { slug } = spot
return `${slug} New Issue`
}

tableIcon(_: GitHubPRNewCommentSpot): string {
return '🔄' // PR icon TODO: icon urls in /public
}

buildUrl(spot: GitHubPRNewCommentSpot): string {
return `https://${spot.domain}/${spot.slug}/issue/new`
}
}
31 changes: 24 additions & 7 deletions browser-extension/src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CONFIG } from './config'
import { CONFIG, type LogLevel } from './config'

/**
* Simple logging utilities for the extension
Expand All @@ -9,12 +9,29 @@ const prefix = `[${CONFIG.EXTENSION_NAME}]`
// No-op function for disabled logging
const noop = () => {}

// Log level hierarchy - index represents priority
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
DEBUG: 0,
ERROR: 3,
INFO: 1,
WARN: 2,
}

// Helper function to check if a log level is enabled
const shouldLog = (level: LogLevel): boolean => {
// Don't log anything in production mode
if (CONFIG.MODE === 'PROD') {
return false
}
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[CONFIG.LOG_LEVEL]
}

// Export simple logging functions
export const logger = {
debug: CONFIG.DEBUG ? console.log.bind(console, prefix) : noop,
error: console.error.bind(console, prefix),
info: CONFIG.DEBUG ? console.log.bind(console, prefix) : noop,
time: CONFIG.DEBUG ? console.time.bind(console) : noop,
timeEnd: CONFIG.DEBUG ? console.timeEnd.bind(console) : noop,
warn: console.warn.bind(console, prefix),
debug: shouldLog('DEBUG') ? console.log.bind(console, prefix) : noop,
error: shouldLog('ERROR') ? console.error.bind(console, prefix) : noop,
info: shouldLog('INFO') ? console.log.bind(console, prefix) : noop,
time: shouldLog('INFO') ? console.time.bind(console) : noop,
timeEnd: shouldLog('INFO') ? console.timeEnd.bind(console) : noop,
warn: shouldLog('WARN') ? console.warn.bind(console, prefix) : noop,
}
4 changes: 4 additions & 0 deletions browser-extension/src/lib/registries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { OverTypeInstance } from 'overtype'
import type { CommentEnhancer, CommentSpot } from './enhancer'
import { GitHubIssueAddCommentEnhancer } from './enhancers/github/githubIssueAddComment'
import { GitHubIssueNewCommentEnhancer } from './enhancers/github/githubIssueNewComment'
import { GitHubPRAddCommentEnhancer } from './enhancers/github/githubPRAddComment'
import { GitHubPRNewCommentEnhancer } from './enhancers/github/githubPRNewComment'

export interface EnhancedTextarea<T extends CommentSpot = CommentSpot> {
textarea: HTMLTextAreaElement
Expand All @@ -18,7 +20,9 @@ export class EnhancerRegistry {
constructor() {
// Register all available handlers
this.register(new GitHubIssueAddCommentEnhancer())
this.register(new GitHubIssueNewCommentEnhancer())
this.register(new GitHubPRAddCommentEnhancer())
this.register(new GitHubPRNewCommentEnhancer())
}

private register<T extends CommentSpot>(enhancer: CommentEnhancer<T>): void {
Expand Down
2 changes: 1 addition & 1 deletion browser-extension/tests/har-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export { expect }
export function usingHar(harKey: keyof typeof PAGES) {
return {
it: (name: string, fn: () => void | Promise<void>) => {
return baseTest(name, async () => {
return baseTest(`${harKey}:${name}`, async () => {
// Setup HAR DOM before test
await setupHarDOM(harKey)

Expand Down
26 changes: 26 additions & 0 deletions browser-extension/tests/lib/enhancers/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ describe('github', () => {
}
`)
})
usingHar('gh_new_pr').it('should create the correct spot object', async () => {
const enhancers = new EnhancerRegistry()
const textareas = document.querySelectorAll('textarea')
expect(textareas.length).toBe(2)
expect(enhancers.tryToEnhance(textareas[0]!)?.spot).toMatchInlineSnapshot(`
{
"domain": "github.com",
"slug": "diffplug/selfie/main...cavia-porcellus:selfie:main",
"type": "GH_PR_NEW_COMMENT",
"unique_key": "github.com:diffplug/selfie/main...cavia-porcellus:selfie:main",
}
`)
})
usingHar('gh_issue').it('should create the correct spot object', async () => {
const enhancers = new EnhancerRegistry()
const textareas = document.querySelectorAll('textarea')
Expand All @@ -35,4 +48,17 @@ describe('github', () => {
}
`)
})
usingHar('gh_new_issue').it('should create the correct spot object', async () => {
const enhancers = new EnhancerRegistry()
const textareas = document.querySelectorAll('textarea')
expect(textareas.length).toBe(1)
expect(enhancers.tryToEnhance(textareas[0]!)?.spot).toMatchInlineSnapshot(`
{
"domain": "github.com",
"slug": "diffplug/selfie",
"type": "GH_ISSUE_NEW_COMMENT",
"unique_key": "github.com:diffplug/selfie:new",
}
`)
})
})
2 changes: 1 addition & 1 deletion browser-extension/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default defineConfig({
plugins: [WxtVitest()],
test: {
environment: 'node',
pool: 'threads',
globals: true,
pool: 'threads',
},
})