2b0132a81b0b

remove 5 orphaned files: content/palette.ts, content/field-scoring.ts, popup/components/{Pip,Ring,TOTP}.tsx

time
2026-05-09 00:52 UTC · 5 months ago
machine
koh2
parent
0b15ab269010
5 files src/content/field-scoring.ts+0 −252src/content/palette.ts+0 −2src/popup/components/Pip.tsx+0 −14src/popup/components/Ring.tsx+0 −84src/popup/components/TOTP.tsx+0 −33
src/content/field-scoring.ts+0 −252
@@ -1,252 +0,0 @@1-export interface FieldScore {2-  element: HTMLInputElement;3-  score: number;4-  fieldType: "username" | "password" | "email" | "totp" | "unknown";5-  factors: {6-    autocompleteMatch: number;7-    typeMatch: number;8-    namePattern: number;9-    idPattern: number;10-    placeholderPattern: number;11-    ariaLabelPattern: number;12-    siblingContext: number;13-  };14-}15-16-export interface FormFields {17-  username: HTMLInputElement | null;18-  password: HTMLInputElement | null;19-  email: HTMLInputElement | null;20-  totp: HTMLInputElement | null;21-}22-23-const USERNAME_PATTERNS = [24-  /user/i,25-  /login/i,26-  /usr/i,27-  /uname/i,28-  /account/i,29-  /acct/i,30-  /signin/i,31-  /sign-in/i,32-  /session/i,33-  /member/i,34-  /alias/i,35-];36-37-const EMAIL_PATTERNS = [/email/i, /e-mail/i, /mail/i];38-39-const PASSWORD_PATTERNS = [40-  /pass/i,41-  /password/i,42-  /pwd/i,43-  /secret/i,44-  /passphrase/i,45-  /key/i,46-  /credential/i,47-];48-49-const TOTP_PATTERNS = [50-  /totp/i,51-  /otp/i,52-  /2fa/i,53-  /mfa/i,54-  /two-factor/i,55-  /twofactor/i,56-  /authenticat/i,57-  /verification/i,58-  /code/i,59-  /pin/i,60-  /token/i,61-];62-63-const AUTOCOMPLETE_VALUES: Record<64-  "username" | "email" | "password" | "totp",65-  string[]66-> = {67-  username: ["username", "login", "user"],68-  email: ["email", "username"],69-  password: ["current-password", "password", "new-password"],70-  totp: ["one-time-code", "totp"],71-};72-73-function matchesPattern(value: string, patterns: RegExp[]): boolean {74-  return patterns.some((pattern) => pattern.test(value));75-}76-77-function getPatternsForType(78-  type: "username" | "password" | "email" | "totp"79-): RegExp[] {80-  switch (type) {81-    case "username":82-      return USERNAME_PATTERNS;83-    case "password":84-      return PASSWORD_PATTERNS;85-    case "email":86-      return EMAIL_PATTERNS;87-    case "totp":88-      return TOTP_PATTERNS;89-  }90-}91-92-function getLabelText(labelledById: string): string {93-  const label = document.getElementById(labelledById);94-  return label?.textContent || "";95-}96-97-function analyzeSiblingContext(input: HTMLInputElement, patterns: RegExp[]): number {98-  const parent = input.parentElement;99-  if (!parent) return 0;100-101-  for (const sibling of Array.from(parent.children)) {102-    if (sibling === input) continue;103-    if (matchesPattern(sibling.textContent || "", patterns)) return 5;104-  }105-106-  const grandparent = parent.parentElement;107-  if (!grandparent) return 0;108-  for (const sibling of Array.from(grandparent.children)) {109-    if (sibling.contains(input)) continue;110-    if (matchesPattern(sibling.textContent || "", patterns)) return 5;111-  }112-113-  return 0;114-}115-116-function calculateFieldScore(117-  input: HTMLInputElement,118-  type: "username" | "password" | "email" | "totp"119-): number {120-  const patterns = getPatternsForType(type);121-  let score = 0;122-  const autocomplete = input.getAttribute("autocomplete") || "";123-  const expected = AUTOCOMPLETE_VALUES[type] || [];124-125-  if (expected.some((value) => autocomplete.includes(value))) score += 10;126-  if (type === "password" && input.type === "password") score += 8;127-  if (type === "email" && input.type === "email") score += 8;128-  if (type === "totp" && (input.type === "tel" || input.inputMode === "numeric")) {129-    score += 6;130-  }131-132-  const name = input.name || "";133-  const id = input.id || "";134-  const placeholder = input.placeholder || "";135-  const ariaLabel = input.getAttribute("aria-label") || "";136-  const ariaLabelledBy = input.getAttribute("aria-labelledby") || "";137-138-  if (matchesPattern(name, patterns)) score += 6;139-  if (matchesPattern(id, patterns)) score += 6;140-  if (matchesPattern(placeholder, patterns)) score += 4;141-  if (matchesPattern(ariaLabel, patterns)) score += 4;142-  if (ariaLabelledBy && matchesPattern(getLabelText(ariaLabelledBy), patterns)) {143-    score += 4;144-  }145-146-  score += analyzeSiblingContext(input, patterns);147-  return score;148-}149-150-export function scoreInputField(input: HTMLInputElement): FieldScore {151-  const types: Array<"username" | "password" | "email" | "totp"> = [152-    "username",153-    "password",154-    "email",155-    "totp",156-  ];157-158-  const scores = types.map((type) => {159-    const factors = {160-      autocompleteMatch: 0,161-      typeMatch: 0,162-      namePattern: 0,163-      idPattern: 0,164-      placeholderPattern: 0,165-      ariaLabelPattern: 0,166-      siblingContext: 0,167-    };168-    const autocomplete = input.getAttribute("autocomplete") || "";169-    const patterns = getPatternsForType(type);170-    const name = input.name || "";171-    const id = input.id || "";172-    const placeholder = input.placeholder || "";173-    const ariaLabel = input.getAttribute("aria-label") || "";174-    if (AUTOCOMPLETE_VALUES[type]?.some((value) => autocomplete.includes(value))) {175-      factors.autocompleteMatch = 10;176-    }177-    if (type === "password" && input.type === "password") factors.typeMatch = 8;178-    if (type === "email" && input.type === "email") factors.typeMatch = 8;179-    if (type === "totp" && (input.type === "tel" || input.inputMode === "numeric")) {180-      factors.typeMatch = 6;181-    }182-    if (matchesPattern(name, patterns)) factors.namePattern = 6;183-    if (matchesPattern(id, patterns)) factors.idPattern = 6;184-    if (matchesPattern(placeholder, patterns)) factors.placeholderPattern = 4;185-    if (matchesPattern(ariaLabel, patterns)) factors.ariaLabelPattern = 4;186-    factors.siblingContext = analyzeSiblingContext(input, patterns);187-188-    return {189-      type,190-      score: calculateFieldScore(input, type),191-      factors,192-    };193-  });194-195-  scores.sort((a, b) => b.score - a.score);196-  const best = scores[0];197-  return {198-    element: input,199-    score: best.score,200-    fieldType: best.score > 5 ? best.type : "unknown",201-    factors: best.factors,202-  };203-}204-205-export function identifyLoginFields(form: HTMLFormElement | null): FormFields {206-  const inputs = Array.from(207-    (form || document).querySelectorAll<HTMLInputElement>(208-      'input:not([type="hidden"]):not([type="submit"]):not([type="button"])'209-    )210-  );211-212-  const scoredFields = inputs.map(scoreInputField).sort((a, b) => b.score - a.score);213-  const result: FormFields = {214-    username: null,215-    password: null,216-    email: null,217-    totp: null,218-  };219-  const used = new Set<HTMLInputElement>();220-221-  for (const field of scoredFields) {222-    if (used.has(field.element) || field.score < 3) continue;223-    switch (field.fieldType) {224-      case "username":225-        if (!result.username) {226-          result.username = field.element;227-          used.add(field.element);228-        }229-        break;230-      case "password":231-        if (!result.password) {232-          result.password = field.element;233-          used.add(field.element);234-        }235-        break;236-      case "email":237-        if (!result.email && !result.username) {238-          result.email = field.element;239-          used.add(field.element);240-        }241-        break;242-      case "totp":243-        if (!result.totp) {244-          result.totp = field.element;245-          used.add(field.element);246-        }247-        break;248-    }249-  }250-251-  return result;252-}

view file at the parent

src/content/palette.ts+0 −2
@@ -1,2 +0,0 @@1-// This file has been retired. Logic was consolidated into inject.ts and index.ts.2-export {};

view file at the parent

src/popup/components/Pip.tsx+0 −14
@@ -1,14 +0,0 @@1-import { createSignal } from "solid-js";2-3-export function Pip() {4-  const [pulsing, setPulsing] = createSignal(false);5-6-  function pulse() {7-    setPulsing(true);8-    setTimeout(() => setPulsing(false), 400);9-  }10-11-  (window as any).__peachPipPulse = pulse;12-13-  return <span class={`pip ${pulsing() ? "pulse" : ""}`} />;14-}

view file at the parent

src/popup/components/Ring.tsx+0 −84
@@ -1,84 +0,0 @@1-import { onMount, onCleanup } from "solid-js";2-3-interface RingProps {4-  target: () => HTMLElement | null;5-}6-7-export function Ring(props: RingProps) {8-  let svgEl: SVGSVGElement | undefined;9-  let rectEl: SVGRectElement | undefined;10-  let fadeTimer: ReturnType<typeof setTimeout>;11-12-  function calibrate() {13-    const el = props.target();14-    if (!el || !svgEl || !rectEl) return;15-    const { width, height } = el.getBoundingClientRect();16-    const r = 12;17-    const perim = 2 * (width + height);18-    svgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);19-    svgEl.setAttribute("width", String(width));20-    svgEl.setAttribute("height", String(height));21-    rectEl.setAttribute("width", String(width));22-    rectEl.setAttribute("height", String(height));23-    rectEl.setAttribute("rx", String(r));24-    rectEl.setAttribute("ry", String(r));25-    rectEl.style.setProperty("--perim", String(perim));26-    rectEl.style.strokeDasharray = String(perim);27-    rectEl.style.strokeDashoffset = String(perim);28-  }29-30-  function trigger() {31-    if (!rectEl) return;32-    rectEl.style.animation = "none";33-    void (rectEl as unknown as HTMLElement).offsetWidth;34-    rectEl.style.animation = "ringDraw 380ms ease-out both";35-    clearTimeout(fadeTimer);36-    fadeTimer = setTimeout(beginFade, 380 + 1200);37-  }38-39-  function beginFade() {40-    if (!rectEl) return;41-    rectEl.style.transition = "opacity 600ms var(--retreat)";42-    rectEl.style.opacity = "0";43-  }44-45-  onMount(() => {46-    calibrate();47-    const ro = new ResizeObserver(calibrate);48-    const el = props.target();49-    if (el) ro.observe(el);50-    onCleanup(() => ro.disconnect());51-  });52-53-  (window as any).__peachRingTrigger = trigger;54-55-  return (56-    <svg57-      ref={svgEl}58-      style={{ position: "absolute", inset: 0, "pointer-events": "none", "z-index": 10 }}59-    >60-      <style>{`61-        @keyframes ringDraw {62-          0% { stroke-dashoffset: var(--perim); opacity: 0; }63-          60% { stroke-dashoffset: calc(var(--perim) * -0.035); opacity: 0.85; }64-          78% { stroke-dashoffset: calc(var(--perim) * 0.008); }65-          100% { stroke-dashoffset: 0; opacity: 0.85; }66-        }67-      `}</style>68-      <rect69-        ref={rectEl}70-        x="0"71-        y="0"72-        fill="none"73-        stroke="var(--ac)"74-        stroke-width="1"75-        style={{76-          filter: "drop-shadow(0 0 5px color-mix(in srgb, var(--peach-accent) 80%, transparent))",77-          transform: "rotate(-90deg)",78-          "transform-origin": "50% 50%",79-          opacity: "0",80-        }}81-      />82-    </svg>83-  );84-}

view file at the parent

src/popup/components/TOTP.tsx+0 −33
@@ -1,33 +0,0 @@1-import { createSignal, onCleanup } from "solid-js";2-import { generateTOTP } from "../../crypto/totp";3-4-interface TOTPProps {5-  secret: string;6-}7-8-export function TOTPDisplay(props: TOTPProps) {9-  const [totp, setTotp] = createSignal(generateTOTP(props.secret));10-11-  const interval = setInterval(() => {12-    setTotp(generateTOTP(props.secret));13-  }, 1000);14-15-  onCleanup(() => clearInterval(interval));16-17-  return (18-    <div class="peach-totp-band">19-      <div>20-        <span class="peach-label">2-factor</span>21-        <p class="peach-totp-code">22-          <span class="peach-totp-code-text">23-            {totp().code.slice(0, 3)} {totp().code.slice(3)}24-          </span>25-        </p>26-      </div>27-      <div class="peach-totp-meta">28-        <span class="peach-label">expires</span>29-        <div class="peach-totp-countdown">{totp().remaining}s</div>30-      </div>31-    </div>32-  );33-}

view file at the parent