# 沙拉查词项目规范

> 架构原则、代码风格、通信协议、安全规范

---

## 1. 架构原则

### 模块职责

| 模块 | 职责 | 禁止行为 |
|------|------|----------|
| **Background** | 网络请求、数据持久化、右键菜单 | 操作DOM |
| **Content Script** | 读取页面文本、注入UI、用户交互 | 直接跨域请求 |
| **Popup** | 独立查词、快速设置、历史记录 | 访问页面DOM |
| **Options** | 配置管理、生词本、词典账号 | 操作页面内容 |
| **Shared** | 工具函数、常量、基类 | 依赖特定模块 |

### 通信规则
```
Content ↔ Background: chrome.runtime.sendMessage
Popup ↔ Background: chrome.runtime.sendMessage
Options → Background: chrome.runtime.sendMessage
```

---

## 2. 代码风格

### 命名规范

| 类型 | 规范 | 示例 |
|------|------|------|
| 文件 | 小写+连字符 | `salad-icon.js` |
| 类 | PascalCase | `DictionaryBase` |
| 函数 | camelCase+动词开头 | `getSelectionCoords()` |
| 常量 | UPPER_SNAKE_CASE | `DEFAULT_TIMEOUT` |
| 私有 | 下划线前缀 | `_handleMessage()` |
| 消息类型 | 大写+点分隔 | `DICT.SEARCH` |

### JavaScript 规范

**必须使用**:
- `const` / `let`，禁止 `var`
- 箭头函数作为回调
- 模板字符串
- 解构赋值
- 可选链 `?.`

**禁止**:
- `eval()`, `new Function()`
- `innerHTML` 插入不可信内容
- 同步的 `chrome.storage` 调用

**异步规范**:
```javascript
// ✅ async/await
async function searchWord(word) {
  try {
    const result = await dict.search(word);
    return result;
  } catch (error) {
    console.error('Search failed:', error);
    throw error;
  }
}
```

### CSS 规范

**Shadow DOM 隔离**:
```javascript
const shadow = element.attachShadow({ mode: 'open' });
shadow.innerHTML = `
  <style>
    :host { display: block; }
    .container { background: var(--salad-bg, #fff); }
  </style>
  <div class="container">...</div>
`;
```

**CSS 变量**:
```css
:root {
  --salad-primary: #4CAF50;
  --salad-bg: #ffffff;
  --salad-text: #333333;
}
[data-theme="dark"] {
  --salad-bg: #1a1a1a;
  --salad-text: #e0e0e0;
}
```

---

## 3. 通信协议

### 消息格式
```javascript
// 请求
{
  type: 'NAMESPACE.ACTION',
  payload: { },
  meta: { timestamp, requestId }
}

// 响应
{
  type: 'NAMESPACE.ACTION_RESPONSE',
  payload: { },
  error: null,
  meta: { requestId }
}
```

### 标准消息类型
```javascript
DICT.SEARCH / DICT.SEARCH_RESPONSE
CONFIG.GET / CONFIG.SET / CONFIG.CHANGED
WORD.ADD_FAVORITE / WORD.REMOVE_FAVORITE
CLIPBOARD.READ / CLIPBOARD.WRITE
HTTP.GET / HTTP.POST
```

### 通信封装
```javascript
// shared/messaging.js
class MessageClient {
  async send(type, payload, timeout = 5000) {
    return new Promise((resolve, reject) => {
      // 实现：发送消息，监听响应，超时处理
    });
  }
}
export const messaging = new MessageClient();
```

---

## 4. 词典开发规范

### 基类定义
```javascript
export class DictionaryBase {
  constructor(config = {}) {
    this.name = config.name || 'Unknown';
    this.icon = config.icon || '';
    this.languages = config.languages || ['en', 'zh'];
  }

  async search(word) {
    throw new Error('search() must be implemented');
  }

  supports(lang) {
    return this.languages.includes(lang);
  }
}
```

### 返回格式
```javascript
{
  word: 'hello',
  phonetic: '/həˈləʊ/',
  meanings: [
    { partOfSpeech: 'int.', definitions: ['你好', '喂'] }
  ],
  examples: [
    { sentence: 'Hello, world!', translation: '你好，世界！' }
  ],
  url: 'https://...'
}
```

### 实现模板
```javascript
export class XxxDictionary extends DictionaryBase {
  constructor(config = {}) {
    super({ name: '词典名', icon: 'icon.png', languages: ['en', 'zh'], ...config });
  }

  async search(word) {
    if (!word?.trim()) throw new Error('Word is empty');
    
    const html = await messaging.send('HTTP.GET', {
      url: `https://api.example.com?q=${encodeURIComponent(word)}`
    });
    
    return this.parse(html, word);
  }

  parse(html, word) {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    return {
      word,
      phonetic: this.extractPhonetic(doc),
      meanings: this.extractMeanings(doc),
      examples: this.extractExamples(doc),
      url: ''
    };
  }
}
```

---

## 5. UI 组件规范

### 组件基类
```javascript
export class ComponentBase extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: 'open' });
    this._isVisible = false;
  }

  render() {
    throw new Error('render() must be implemented');
  }

  show(x, y) {
    this._isVisible = true;
    this.style.display = 'block';
    this.style.left = `${x}px`;
    this.style.top = `${y}px`;
  }

  hide() {
    this._isVisible = false;
    this.style.display = 'none';
  }

  destroy() {
    this.hide();
    this.remove();
  }
}
```

---

## 6. 存储规范

### 存储分层
```javascript
chrome.storage.local    // 配置、生词本
chrome.storage.session  // 临时数据
IndexedDB              // 大数据量
Memory                 // 运行时缓存
```

### 配置结构
```javascript
const DEFAULT_CONFIG = {
  version: '7.20.0',
  general: {
    enableSelection: true,
    enableAnimation: true,
    darkMode: false,
    language: 'zh_CN'
  },
  searchMode: {
    triggerMode: 'icon',  // icon/direct/double/key
    iconHoverSearch: false,
    shortcutKeys: []
  },
  dictionaries: [
    { id: 'bing', enabled: true, order: 0 }
  ]
};
```

---

## 7. 安全规范

### XSS 防护
```javascript
// ✅ textContent
element.textContent = userInput;

// ❌ innerHTML 插入不可信内容
element.innerHTML = userInput;

// ✅ 必须使用时转义
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}
```

### CSP
```json
{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}
```

---

## 8. 版本号规范

遵循 SemVer，但 **PATCH 与验收点关联**:

```
MAJOR.MINOR.PATCH
│     │     └─ 每个任务完成 +1
│     └─ 模块完成进入下一模块 +1  
└─ 正式发布 = 1
```

详见 [README.md](./README.md) 版本规则部分和 [VERSION.md](./VERSION.md)
