Skip to content

Commit a6dc4cd

Browse files
committed
fix: 根据 URL 识别特殊页面,保持换行符(contenteditable 用 <br> 处理 \n)
1 parent 78770d8 commit a6dc4cd

File tree

3 files changed

+84
-8
lines changed

3 files changed

+84
-8
lines changed

AGENTS.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
- Source: `entrypoints/` (web extension entrypoints)
5+
- `background.ts`, `content/`, `popup/`, `options/`
6+
- Shared code: `utils/` (e.g., `browser/`, `auth/`, `sync/`), `types/`
7+
- Assets & static: `assets/`, `public/`, `docs/`
8+
- Config: `wxt.config.ts`, `tailwind.config.js`, `tsconfig.json`
9+
- Build output: `.output/` (e.g., `chrome-mv3/`, `firefox-mv2/`)
10+
- Path aliases: use `@/` for project utils/types and `#imports` from WXT.
11+
12+
## Build, Test, and Development Commands
13+
- `pnpm dev` — Start Chrome dev build with hot reload.
14+
- `pnpm dev:firefox` — Start Firefox dev build.
15+
- `pnpm build` / `pnpm build:firefox` — Production builds.
16+
- `pnpm zip` — Zip last build for store upload.
17+
- `pnpm compile` — Type-check with TypeScript.
18+
- Load locally (Chrome): open `chrome://extensions` → Load unpacked → `.output/chrome-mv3/`.
19+
20+
## Coding Style & Naming Conventions
21+
- Language: TypeScript + React (functional components).
22+
- Indentation: 2 spaces; use double quotes for strings; semicolons permitted.
23+
- Files: libraries `.ts`; components `.tsx`. Component files in PascalCase; exports in camelCase.
24+
- Imports: prefer path aliases (e.g., `@/utils/...`, `~/assets/tailwind.css`); group std/external/local.
25+
- No ESLint/Prettier in repo; keep diffs small and consistent with nearby code.
26+
27+
## Testing Guidelines
28+
- No formal test runner yet. Before PRs: `pnpm compile` must pass.
29+
- Manual QA: verify `/p` selector in pages (content), options CRUD, category/pin/sort, Notion sync toggles.
30+
- Confirm both Chrome and Firefox builds launch without console errors.
31+
32+
## Commit & Pull Request Guidelines
33+
- Commits: Conventional-like style; emojis allowed (e.g., `✨ feat: ...`, `fix: ...`). Keep them small and focused; reference issues (`#123`) when relevant.
34+
- PRs: include clear description, rationale, steps to reproduce/verify, screenshots/GIFs for UI changes, and mention affected entrypoints.
35+
36+
## Security & Configuration Tips
37+
- Never commit secrets. Copy `.env.example``.env` and set: `WXT_CHROME_APP_CLIENT_ID_PREFIX`, `WXT_WEB_APP_CLIENT_ID_PREFIX`, `WXT_FIREFOX_EXTENSION_ID`.
38+
- OAuth scopes and IDs are assembled in `wxt.config.ts`; keep published IDs stable.
39+

entrypoints/content/components/PromptSelector.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { isDarkMode, getCopyShortcutText } from "@/utils/tools";
88
import { getCategories } from "@/utils/categoryUtils";
99
import { getGlobalSetting } from "@/utils/globalSettings";
1010
import { t } from "@/utils/i18n";
11+
import { getNewlineStrategy, setElementContentByStrategy } from "@/utils/newlineRules";
1112

1213
interface PromptSelectorProps {
1314
prompts: PromptItemWithVariables[];
@@ -298,6 +299,7 @@ const PromptSelector: React.FC<PromptSelectorProps> = ({
298299
try {
299300
// contenteditable 元素的特殊处理
300301
const editableElement = (targetElement as any)._element as HTMLElement;
302+
const newlineStrategy = getNewlineStrategy(window.location.href);
301303

302304
// 获取当前内容和光标位置
303305
const fullText = editableElement.textContent || "";
@@ -325,8 +327,7 @@ const PromptSelector: React.FC<PromptSelectorProps> = ({
325327

326328
// 如果 beforeinput 事件没有被阻止,则继续处理
327329
if (editableElement.dispatchEvent(beforeInputEvent)) {
328-
// 设置新内容
329-
editableElement.textContent = newContent;
330+
setElementContentByStrategy(editableElement, newContent, newlineStrategy);
330331

331332
// 创建并分发 input 事件
332333
const inputEvent = new InputEvent("input", {
@@ -360,7 +361,6 @@ const PromptSelector: React.FC<PromptSelectorProps> = ({
360361

361362
// 如果 beforeinput 事件没有被阻止,则继续处理
362363
if (editableElement.dispatchEvent(beforeInputEvent)) {
363-
// 获取当前内容
364364
const currentContent = editableElement.textContent || "";
365365
const position = range.startOffset;
366366

@@ -370,8 +370,7 @@ const PromptSelector: React.FC<PromptSelectorProps> = ({
370370
content +
371371
currentContent.slice(position);
372372

373-
// 设置新内容
374-
editableElement.textContent = newContent;
373+
setElementContentByStrategy(editableElement, newContent, newlineStrategy);
375374

376375
// 创建并分发 input 事件
377376
const inputEvent = new InputEvent("input", {
@@ -401,9 +400,7 @@ const PromptSelector: React.FC<PromptSelectorProps> = ({
401400
if (editableElement.dispatchEvent(beforeInputEvent)) {
402401
const currentContent = editableElement.textContent || "";
403402
const newContent = currentContent + content;
404-
405-
// 设置新内容
406-
editableElement.textContent = newContent;
403+
setElementContentByStrategy(editableElement, newContent, newlineStrategy);
407404

408405
// 创建并分发 input 事件
409406
const inputEvent = new InputEvent("input", {

utils/newlineRules.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export type NewlineStrategy = "text" | "br";
2+
3+
type Rule = { pattern: RegExp; strategy: NewlineStrategy };
4+
5+
/**
6+
* 对于 ChatGPT 网页使用 br 换行
7+
*
8+
* fix: https://github.com/wenyuanw/quick-prompt/issues/42
9+
*/
10+
const RULES: Rule[] = [
11+
{ pattern: /chatgpt\.com/i, strategy: "br" },
12+
];
13+
14+
export function getNewlineStrategy(url: string): NewlineStrategy {
15+
const rule = RULES.find((r) => r.pattern.test(url));
16+
return rule ? rule.strategy : "text";
17+
}
18+
19+
function escapeHtml(input: string): string {
20+
return input
21+
.replace(/&/g, "&amp;")
22+
.replace(/</g, "&lt;")
23+
.replace(/>/g, "&gt;")
24+
.replace(/\"/g, "&quot;")
25+
.replace(/'/g, "&#39;");
26+
}
27+
28+
export function setElementContentByStrategy(
29+
el: HTMLElement,
30+
text: string,
31+
strategy: NewlineStrategy
32+
): void {
33+
if (strategy === "br") {
34+
const html = escapeHtml(text).replace(/\n/g, "<br>");
35+
(el as HTMLElement).innerHTML = html;
36+
} else {
37+
(el as HTMLElement).textContent = text;
38+
}
39+
}
40+

0 commit comments

Comments
 (0)