File size: 3,039 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
 * Theme Manager Plugin β€” injects CSS custom properties into HTML responses.
 *
 * Reads the X-User-Theme header from requests, merges with plugin config,
 * and injects CSS variables into HTML responses via a <style> tag.
 *
 * @module theme-manager
 */

const DEFAULT_THEMES = {
  light: {
    "--bg-primary": "#ffffff",
    "--bg-secondary": "#f5f5f5",
    "--text-primary": "#1a1a1a",
    "--text-secondary": "#666666",
    "--border-color": "#e0e0e0",
    "--shadow": "0 1px 3px rgba(0,0,0,0.1)",
  },
  dark: {
    "--bg-primary": "#1a1a1a",
    "--bg-secondary": "#2d2d2d",
    "--text-primary": "#f5f5f5",
    "--text-secondary": "#a0a0a0",
    "--border-color": "#404040",
    "--shadow": "0 1px 3px rgba(0,0,0,0.4)",
  },
};

function resolveThemeMode(darkMode, header) {
  if (header === "light" || header === "dark") return header;
  if (darkMode === "light" || darkMode === "dark") return darkMode;
  return "light";
}

function buildCssVariables(mode, config) {
  const base = DEFAULT_THEMES[mode] || DEFAULT_THEMES.light;
  const vars = { ...base };
  if (config.primaryColor) vars["--color-primary"] = config.primaryColor;
  if (config.borderRadius) vars["--border-radius"] = config.borderRadius;
  if (config.fontFamily) vars["--font-family"] = config.fontFamily;
  return vars;
}

function varsToCss(vars) {
  return Object.entries(vars)
    .map(([k, v]) => `  ${k}: ${v};`)
    .join("\n");
}

/**
 * onRequest hook β€” reads X-User-Theme header and stores resolved mode in metadata.
 */
export function onRequest(ctx) {
  const config = ctx?.config || {};
  const header = ctx?.headers?.["x-user-theme"] || ctx?.headers?.["X-User-Theme"];
  const mode = resolveThemeMode(config.darkMode, header);
  if (ctx?.metadata) {
    ctx.metadata.__themeMode = mode;
  }
}

/**
 * onResponse hook β€” injects <style> tag with CSS variables into HTML responses.
 */
export function onResponse(ctx, response) {
  const config = ctx?.config || {};
  const mode = ctx?.metadata?.__themeMode || "light";

  const isHtml =
    response?.headers?.["content-type"]?.includes("text/html") ||
    typeof response?.body === "string" &&
      (response.body.includes("<!DOCTYPE html>") || response.body.includes("<html"));

  if (!isHtml || typeof response?.body !== "string") return response;

  const vars = buildCssVariables(mode, config);
  const styleTag = `<style id="omniroute-theme">\n:root {\n${varsToCss(vars)}\n}\n</style>`;

  const body = response.body.replace(/<head([^>]*)>/i, `<head$1>${styleTag}`);

  return { ...response, body };
}

/**
 * onInstall lifecycle hook β€” log installation.
 */
export function onInstall(ctx) {
  console.log(`[theme-manager] Installed v${ctx?.version || "unknown"}`);
}

/**
 * onActivate lifecycle hook β€” log activation.
 */
export function onActivate(ctx) {
  console.log(`[theme-manager] Activated with config:`, ctx?.config || {});
}

/**
 * onDeactivate lifecycle hook β€” log deactivation.
 */
export function onDeactivate(ctx) {
  console.log(`[theme-manager] Deactivated`);
}