Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Codex Changelog

## Recent Feature Updates

### ✨ Features & Enhancements

- **Revision Notes System**

- Added the ability to mark specific text selections as "Revision Notes" using the `CTRL+SHIFT+R` (`Cmd+Shift+R` on Mac) shortcut.
- A new "Revision Notes" panel has been added to the editor's sidebar (accessible via a dedicated icon).
- Clicking on a note in the sidebar will automatically scroll the editor and focus on the exact position of the revision.
- The visual style of these notes (background color, borders, etc.) can now be dynamically configured via the `Prefs.ts` file under `revisionNoteStyle`.

- **Section & Paragraph Reordering**

- You can now seamlessly reorganize your document's structure without breaking formatting.
- Hovering over any heading (`H1`, `H2`, `H3`) will reveal discrete "Up" and "Down" arrows on the left side.
- Clicking these arrows will instantly move the entire section (the heading along with all of its inner paragraphs and content) up or down, automatically swapping places with adjacent sections.

- **Text Finder (Search in Page)**

- Added a fully functional "Find in Document" overlay accessible via `CTRL+F` / `CMD+F`.
- Integrated a custom Tiptap Search extension leveraging ProseMirror Decorations to highlight text seamlessly without hijacking the DOM string selection or the user's cursor focus.
- Added auto-scroll to the current match using smooth browser native transitions.
- Supported Keyboard Shortcuts within the Finder input box (`Enter` for next, `Shift+Enter` for previous, `Escape` to close).
- Safely escaped all RegEx characters to support searching for wildcards and punctuation marks without performance bottlenecks.

- **Image Cropper Modal**

- Integrated a robust Image Cropping sub-routine directly into the Editor.
- When selecting an image inside the editor, a new "Crop" button dynamically appears in the toolbar.
- The modal supports freeform cropping using an HTML5 Canvas interface and elegantly replaces the old image with the newly cropped Base64 preview.

- **Dynamic 'Word-Style' Text Styling**

- Transitioned from hardcoded tags to a fully dynamic Custom Styles configuration powered by JSON (`Prefs.ts`).
- Users can now define entirely customized text and heading rules globally mapped within Codex.
- The Editor Toolbar dropdown automatically reads and maps these styles into selectable headings and paragraphs seamlessly.

- **Auto-Save System**

- Implemented an internal interval-based tracker managing the background saving operations of the active note.
- Introduced fully granular controls under Settings to disable Auto-Save or configure its intervals (ranging from 1 to 30 minutes).

- **Settings Menu Redesign & Organization**

- Overhauled `SettingsView.tsx` utilizing Mantine's modern Tab-based interface.
- Split configurations into three accessible macro-categories: **General**, **Editor**, and **Saving**.
- Added a brand new scaling preference (`toolbarSize`) that dynamically alters the size of the Editor Toolbar for accessibility and better clicks.

- **Table of Contents (TOC) UI Uplift**

- Redesigned the TOC container to be scrollable and properly constrained inside the view window, resolving overlap issues for documents spanning many headings.

- **Localization Coverage**
- Added and integrated all strings relating to Auto-Save, Cropper, Editor Width/Border, Text Finder, and Toolbar sizing across all language dictionaries (English `en_US`, Russian `ru_RU`, Simplified Chinese `zh_CN`).

### 🐛 Bug Fixes

- Prevented `CTRL+F` from causing duplicate component mounts if the finder was already active.
- Resolved Z-Index overlapping conflicts between the floating Text Finder and the sticky Editor Toolbar.
- Fixed an issue where the generic Auto-Save feature could crash if trigged while switching components globally.
32 changes: 32 additions & 0 deletions MISSING_FILES_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
### 🛠️ How to migrate/restore old notes if they don't show up in the app

If you manually moved your old note files into the `~/.config/codex/notes/` folder but Codex isn't showing them, **don't panic, your data is safe.** This happens because Codex uses an internal database/registry to keep track of notes. If you just drop a file into the folder, the app won't recognize it because it's not registered in the database.

To fix this, we can do a quick "file transplant" using the terminal. Here is the step-by-step workaround:

#### Step 1: Register a new note in the app
1. Open the new version of Codex.
2. Create a **New Note** (you can name it something like *"Recovered Note"*).
3. Close the app completely. This forces Codex to register the new note in its database.

#### Step 2: Find the internal file name
1. Open your terminal.
2. Run the following command to find the newly created file (it will be at the top of the list and will have a random string name):
```bash
ls -lt ~/.config/codex/notes/ | head -n 5
```
3. Copy that random filename.

#### Step 3: Overwrite the file (The Transplant)
Now, we will overwrite the empty registered note with your old note data.
Run the `cp` (copy) command in your terminal like this:

```bash
cp /path/to/your/OLD_NOTE_FILE ~/.config/codex/notes/THE_NEW_RANDOM_NAME
```
*(Make sure to replace `/path/to/your/OLD_NOTE_FILE` with the actual path of your old file, and `THE_NEW_RANDOM_NAME` with the file name you found in Step 2).*

#### Step 4: You're done!
Open Codex again. Click on the *"Recovered Note"* you created in Step 1, and you will see all your old content perfectly restored!

> **Note on Custom Styles:** If you are upgrading to the new version containing the `CustomStyle` extension, you don't need to worry about formatting crashes. The extension is built with backward compatibility (`default: null`), so it will automatically read and safely upgrade your old files without any errors.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"lowlight": "^3.1.0",
"mathlive": "^0.104.0",
"palettey": "^1.0.4",
"react-image-crop": "^11.0.10",
"react-lowlight": "^3.0.0",
"sanitize-filename": "^1.6.3",
"semver": "^7.5.4",
Expand Down
84 changes: 80 additions & 4 deletions packages/common/Locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ export type Locale = {
open_pdf_on_export: string;
saving_section: string;
autosave_page_on_switch: string;
autosave: string;
autosave_interval: string;
general: string;
editor: string;
save_folder: string;
editor_width: string;
toolbar_size: string;
editor_border: string;
editor_spellcheck: string;
save_folder_location: string;
Expand Down Expand Up @@ -191,6 +194,7 @@ export type Locale = {
align_center: string;
align_justified: string;
image: string;
crop: string;
paragraph: string;
blockquote: string;
heading: string;
Expand Down Expand Up @@ -258,7 +262,22 @@ export type Locale = {
create_link: string;
url: string;
};
cropModal: {
title: string;
cancel: string;
save: string;
};
textFinder: {
find: string;
next: string;
previous: string;
no_results: string;
};
code_block_collapse: string;
revision_notes: string;
no_revision_notes: string;
move_up: string;
move_down: string;
};
unsavedChangesDialog: {
title: (name: string) => string;
Expand Down Expand Up @@ -316,13 +335,16 @@ export const locales: Record<SupportedLocales, Locale> = {
use_typography_extension: "Use Typography extension in the Editor",
use_typography_description: 'This enables turning things like "(c)" into "©".',
open_pdf_on_export: "Automatically open PDF after exporting",
saving_section: "Saving Pages",
saving_section: "Saving",
autosave_page_on_switch:
"Automatically save the current page when switching between pages/exiting the editor",
autosave: "Auto Save",
autosave_interval: "Auto Save Interval (minutes)",
general: "General",
editor: "Editor",
save_folder: "Save Folder",
editor_width: "Editor Width",
toolbar_size: "Toolbar Size (requires restart)",
editor_border: "Editor Border",
editor_spellcheck: "Editor Spellcheck",
save_folder_location: "Save Folder Location",
Expand Down Expand Up @@ -486,6 +508,7 @@ export const locales: Record<SupportedLocales, Locale> = {
align_center: "Align Center",
align_justified: "Align Justified",
image: "Insert/Replace Image",
crop: "Crop Image",
paragraph: "Set to Paragraph",
blockquote: "Set to Block Quote",
heading: "Heading",
Expand Down Expand Up @@ -554,7 +577,22 @@ export const locales: Record<SupportedLocales, Locale> = {
create_link: "Create Link",
url: "URL"
},
code_block_collapse: "Collapse"
cropModal: {
title: "Crop Image",
cancel: "Cancel",
save: "Save Crop"
},
textFinder: {
find: "Find in document...",
next: "Next",
previous: "Previous",
no_results: "No results"
},
code_block_collapse: "Collapse",
revision_notes: "Revision Notes",
no_revision_notes: "No revision notes found",
move_up: "Move Up",
move_down: "Move Down"
},
unsavedChangesDialog: {
title: (name: string) => `You have unsaved changes to "${name}"`,
Expand Down Expand Up @@ -609,10 +647,13 @@ export const locales: Record<SupportedLocales, Locale> = {
open_pdf_on_export: "完成导出后自动打开PDF文件",
saving_section: "保存页面",
autosave_page_on_switch: "页面切换/退出编辑器时自动保存当前页面",
autosave: "自动保存",
autosave_interval: "自动保存间隔(分钟)",
general: "基础设置",
editor: "编辑器",
save_folder: "保存文件夹",
editor_width: "编辑器宽度",
toolbar_size: "Toolbar Size (requires restart)",
editor_border: "编辑器边框",
editor_spellcheck: "拼写检查",
save_folder_location: "保存位置",
Expand Down Expand Up @@ -775,6 +816,7 @@ export const locales: Record<SupportedLocales, Locale> = {
align_center: "居中对齐",
align_justified: "两端对齐",
image: "插入/替换图片",
crop: "Crop Image",
paragraph: "设置为段落",
blockquote: "设置为块引用",
heading: "标题",
Expand Down Expand Up @@ -842,7 +884,22 @@ export const locales: Record<SupportedLocales, Locale> = {
create_link: "创建超链接",
url: "URL"
},
code_block_collapse: "折叠"
cropModal: {
title: "Crop Image",
cancel: "Cancel",
save: "Save"
},
textFinder: {
find: "Find in document...",
next: "Next",
previous: "Previous",
no_results: "No results"
},
code_block_collapse: "折叠",
revision_notes: "Revision Notes",
no_revision_notes: "No revision notes found",
move_up: "Move Up",
move_down: "Move Down"
},
unsavedChangesDialog: {
title: (name: string) => `您对 "${name}" 有未保存的更改`,
Expand Down Expand Up @@ -897,10 +954,13 @@ export const locales: Record<SupportedLocales, Locale> = {
saving_section: "Сохранение страниц",
autosave_page_on_switch:
"Автоматически сохранять текущую страницу при переключении между страницами или выходе из редактора",
autosave: "Auto Save",
autosave_interval: "Auto Save Interval (minutes)",
general: "Главная",
editor: "Редактор",
save_folder: "Сохранить папку",
editor_width: "Ширина редактора",
toolbar_size: "Toolbar Size (requires restart)",
editor_border: "Граница редактора",
editor_spellcheck: "Проверка орфографии в редакторе",
save_folder_location: "Место сохранения папок",
Expand Down Expand Up @@ -1063,6 +1123,7 @@ export const locales: Record<SupportedLocales, Locale> = {
align_center: "Выровнять по центру",
align_justified: "Выравнивание по ширине",
image: "Вставить/заменить изображение",
crop: "Crop Image",
paragraph: "Установить на абзац",
blockquote: "Установить на блок цитаты",
heading: "Заголовок",
Expand Down Expand Up @@ -1131,7 +1192,22 @@ export const locales: Record<SupportedLocales, Locale> = {
create_link: "Создать ссылку",
url: "URL"
},
code_block_collapse: "Свернуть"
cropModal: {
title: "Crop Image",
cancel: "Cancel",
save: "Save"
},
textFinder: {
find: "Find in document...",
next: "Next",
previous: "Previous",
no_results: "No results"
},
code_block_collapse: "Свернуть",
revision_notes: "Revision Notes",
no_revision_notes: "No revision notes found",
move_up: "Move Up",
move_down: "Move Down"
},
unsavedChangesDialog: {
title: (name: string) => `Вы имеете несохранённые изменения в "${name}"`,
Expand Down
45 changes: 45 additions & 0 deletions packages/common/Prefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class GeneralPrefs {
theme: "light" | "dark" = "light";
titlebarStyle: "custom" | "native" = "custom";
autoSaveOnPageSwitch = true;
autoSave = false;
autoSaveInterval = 5;
}

class EditorPrefs {
Expand All @@ -21,6 +23,49 @@ class EditorPrefs {
recentCodeLangs: string[] = [];
codeWordWrap = false;
tabSize = 4;
toolbarSize: "sm" | "md" | "lg" = "md";
customTextStyles: Record<string, Record<string, string>> = {
Normal: {
"tag": "p",
"font-size": "default",
"font-weight": "normal",
"font-style": "normal",
"color": "inherit"
},
"Heading 1": {
"tag": "h1",
"font-size": "32px",
"font-weight": "bold",
},
"Heading 2": {
"tag": "h2",
"font-size": "24px",
"font-weight": "bold",
},
Subtitle: {
"tag": "p",
"font-size": "18px",
"color": "gray",
"font-style": "italic",
},
Quote: {
"tag": "blockquote",
"font-size": "16px",
"font-style": "italic",
"background-color": "#f0f0f0",
"padding": "4px 8px",
"border-left": "4px solid #ccc"
},
Highlight: {
"background-color": "#ffff00",
"color": "#000000"
}
};
revisionNoteStyle: Record<string, string> = {
"background-color": "rgba(255, 235, 59, 0.3)",
"color": "inherit",
"border-bottom": "2px dashed #fbc02d"
};
}

class MiscPrefs {
Expand Down
26 changes: 25 additions & 1 deletion packages/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function App() {
const fakeEditor = useRef<Editor>(
new Editor({
editable: false,
extensions: extensions({ useTypography: false, tabSize: 4 })
extensions: extensions({ useTypography: false, tabSize: 4, customStyles: {} })
})
);

Expand Down Expand Up @@ -381,6 +381,30 @@ export function App() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (!prefs.current.general.autoSave) return;
const intervalMs = prefs.current.general.autoSaveInterval * 60 * 1000;
const interval = setInterval(() => {
if (unsavedChanges.current && activePage) {
saveActivePage();
notifications.show({
id: activePage.id + "-autosave",
message: (
<Text truncate>
{locale.notifications.saved} {activePage.name} (Auto)
</Text>
),
color: "blue",
icon: <Icon icon="file-check" />,
autoClose: 2000,
withBorder: true
});
}
}, intervalMs);
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [prefs.current.general.autoSave, prefs.current.general.autoSaveInterval, activePage]);

return (
<AppContext.Provider
value={{
Expand Down
Loading