Compare commits

...

259 Commits

Author SHA1 Message Date
627d98c199 Update README with new AI features and enhancements 2026-03-14 00:35:21 +01:00
5db45375f3 Add sanitizeAiResponse function call in App.tsx 2026-03-14 00:34:24 +01:00
cb814d94fa Add function to sanitize AI response 2026-03-14 00:33:21 +01:00
4393a40ad2 Add AI action dispatch and scope handling in App.tsx 2026-03-14 00:29:20 +01:00
7e854bd5d1 Remove unused AI action handling functions 2026-03-14 00:29:16 +01:00
1c664523a6 Add styles for modal selection preview 2026-03-14 00:15:03 +01:00
ebaa428f05 Update AI edit scope modal and custom prompt handling 2026-03-14 00:14:52 +01:00
97e9b26251 Update keydown event handling in src/App.tsx 2026-03-14 00:14:43 +01:00
73beaea077 Remove unused function openCustomPromptLightbox in src/App.tsx 2026-03-14 00:14:43 +01:00
9a42468cfd Update custom prompt handling in App.tsx 2026-03-14 00:14:29 +01:00
2b0023d3e1 Add AI action dispatch and scope handling in App.tsx 2026-03-14 00:14:22 +01:00
790848342c Enhance AI action handling with scope and selection support 2026-03-14 00:14:12 +01:00
f4e79276f0 Refactor state management in App.tsx 2026-03-14 00:13:53 +01:00
ef1201f753 Add types for AI action request and related components 2026-03-14 00:13:47 +01:00
d128ac92dc Add custom prompt feature and enhance keyboard handling 2026-03-14 00:06:44 +01:00
4432251158 Add custom prompt handling and update AI tools menu 2026-03-14 00:06:34 +01:00
79cd72f24f Add openCustomPromptLightbox function 2026-03-14 00:06:34 +01:00
727ef480c5 Add custom prompt state and handlers 2026-03-14 00:06:34 +01:00
2c20ab748e Add persistCurrentTextVersion function and update runAiAction to use it 2026-03-13 23:32:55 +01:00
cd2c4d3782 Remove unused variable in App.tsx 2026-03-13 23:14:20 +01:00
507a6f9f9a Update CSS for settings panel and prompt accordion 2026-03-13 23:09:31 +01:00
ee36277c21 Simplify and optimize AI prompt template deletion logic 2026-03-13 23:09:24 +01:00
f7a5c21d52 Ensure expandedPromptId is valid when aiPromptTemplates change 2026-03-13 23:09:19 +01:00
e5e24eb7f3 Reset expandedPromptId default value in App.tsx 2026-03-13 23:09:13 +01:00
07cfb06ee6 Update button disabled condition in App.tsx 2026-03-13 23:09:13 +01:00
6d414d1592 Refactor prompt management in App.tsx 2026-03-13 23:09:02 +01:00
f255f9a5a1 Simplify runAiAction and update AI tool handling 2026-03-13 23:08:47 +01:00
7df435d18d Refactor AI prompt handling in App.tsx 2026-03-13 23:08:19 +01:00
ffb83f9c71 Add function to get AI prompt template label and update state management 2026-03-13 23:08:12 +01:00
a4e863751b Refactor AI prompt handling and storage 2026-03-13 23:08:05 +01:00
e2b739ec6b Update settings panel width and add new styles 2026-03-13 22:58:31 +01:00
18f8ccc62a Refactor prompt section in App.tsx 2026-03-13 22:58:23 +01:00
625be7503d Update button click handler and label in App.tsx 2026-03-13 22:58:23 +01:00
4da563df70 Refactor AI action handling and add AI tools menu 2026-03-13 22:58:02 +01:00
f0765c1f42 Add functions to update AI prompt and add style preset 2026-03-13 22:57:01 +01:00
43e1fb1c79 Update App.tsx with new state variables and localStorage handling 2026-03-13 22:56:50 +01:00
b211342e8a Add AI prompt definitions and related functionality 2026-03-13 22:56:45 +01:00
f5f1b76bfb Enhance database export functionality and update CSS styles 2026-03-13 21:57:48 +01:00
d5bfefefd1 Refactor settings panel layout and add export database functionality 2026-03-13 21:57:42 +01:00
a7d5b58dbf Implement database export functionality 2026-03-13 21:57:08 +01:00
6fa837ab61 Reset database export status when settings are closed 2026-03-13 21:57:08 +01:00
a3947147d6 Add database export state variables 2026-03-13 21:57:08 +01:00
f9bea7ecaa Update App.tsx to include database export functionality 2026-03-13 21:56:44 +01:00
b40c8eae06 Add function to export database snapshot 2026-03-13 21:56:44 +01:00
14e1f40ecf Add logic to focus editor after creating new text 2026-03-13 21:54:19 +01:00
b2b0afdaf6 Add --md-surface-alt variable to markdown-render.css 2026-03-12 16:17:00 +01:00
f1c96d66e6 Add light theme styles for editor textarea wrap and markdown preview 2026-03-12 16:15:54 +01:00
e92af02673 Update markdown root surface alt color for dark theme 2026-03-12 16:14:42 +01:00
db20dd5107 Add background styling for dark theme in editor and markdown preview 2026-03-12 16:14:42 +01:00
56ab395eb2 Update CSS for dark theme in editor components 2026-03-12 16:12:33 +01:00
7671d6e39a Update editor theme extension in App.tsx 2026-03-12 16:00:54 +01:00
c8c7814db4 Update editor theme handling and caret color in CSS 2026-03-12 15:59:31 +01:00
7bbdab340c Add editor theme extension based on theme in App.tsx 2026-03-12 15:59:25 +01:00
41393e59bb Add themeCompartmentRef to App.tsx 2026-03-12 15:59:25 +01:00
fbd573d9e5 auto-git:
[change] src/index.css
2026-03-12 15:56:00 +01:00
30727b4bee auto-git:
[change] src/App.tsx
2026-03-11 11:08:51 +01:00
c7b7c40137 Update dependencies and add search functionality 2026-03-11 11:08:51 +01:00
e0e86b9199 auto-git:
[change] .gitignore
2026-03-11 10:29:11 +01:00
7d8d7f6110 promptDB gelöscht 2026-02-01 06:26:21 +01:00
b7850c270a Refactor main.rs in src-tauri/src/main.rs 2026-02-01 06:20:43 +01:00
a2eb529fcf Add promptDB file 2026-02-01 06:20:43 +01:00
ff5c3073dc Update README.md: remove Ollama link, update AI setup instructions 2026-02-01 05:25:15 +01:00
c4ad259362 Update README.md: clarify Markdown conversion options 2026-02-01 05:25:15 +01:00
9b634ccc05 Clarify no outside network calls in README 2026-02-01 05:25:05 +01:00
cb66d08027 Add Ollama link to README 2026-02-01 05:25:05 +01:00
98b01098a1 Add local AI conversion feature to README 2026-02-01 05:25:05 +01:00
63efadd3fb Add functionality to convert text to Markdown using Ollama API 2026-02-01 05:15:30 +01:00
af01951d9a Remove unused function handleConvertToMarkdown in src/App.tsx 2026-02-01 05:14:46 +01:00
2b4b86816c Update model selection logic in App.tsx 2026-02-01 05:11:08 +01:00
9232054331 Update URL normalization logic in App.tsx 2026-02-01 05:10:49 +01:00
c9edc1e266 Remove unused dependency in App.tsx 2026-02-01 05:10:49 +01:00
a16a4ef0f9 Add styles for settings panel elements 2026-02-01 05:10:15 +01:00
621c7a3681 Add Ollama settings section in App.tsx 2026-02-01 05:10:08 +01:00
4266708e8b Add 'Convert to Markdown' button in App.tsx 2026-02-01 05:10:07 +01:00
57cff46ccd Add Ollama model loading and text conversion functionality 2026-02-01 05:09:49 +01:00
1215ab8fae Add Ollama integration and state management in App.tsx 2026-02-01 05:09:41 +01:00
d9ee24e7cb Update delete logic for text and folders 2026-02-01 04:39:59 +01:00
3a30d6799e Refactor delete text and folder actions in App.tsx 2026-02-01 04:39:52 +01:00
a47eab7a03 Add quick delete functionality for texts and folders 2026-02-01 04:39:44 +01:00
a433510f05 Add folder child count and empty check logic in App.tsx 2026-02-01 04:39:28 +01:00
4413a57c0d Update theme logic in App.tsx 2026-02-01 04:36:11 +01:00
7f2fe4a823 Change option values for theme selection in App.tsx 2026-02-01 04:35:11 +01:00
497738eb73 Update theme state type and logic in App.tsx 2026-02-01 04:35:10 +01:00
3f026499c0 Update state initialization logic in App.tsx 2026-02-01 04:34:44 +01:00
80f928a1f7 Add CSS for print-markdown class 2026-02-01 04:31:39 +01:00
742eecb873 Update editor configuration to consider splitView 2026-02-01 04:31:39 +01:00
9bf4cf1866 Add split editor layout styles 2026-02-01 04:29:48 +01:00
864b3f4c88 Add split view toggle to settings panel in App.tsx 2026-02-01 04:29:43 +01:00
7bee133a9c Update App.tsx to handle markdown preview and split view states 2026-02-01 04:29:38 +01:00
fefa100175 Include splitView in the dependency array for requestMeasure 2026-02-01 04:29:31 +01:00
cf92325bed Add split view state and update localStorage handling 2026-02-01 04:29:31 +01:00
44e64df922 Update README.md: Remove toggle instruction from Markdown preview mode description 2026-02-01 04:27:22 +01:00
7e2964e9f9 Remove unused import in src/App.tsx 2026-02-01 04:25:16 +01:00
2f84b8bce1 Remove unused tab keymap functionality in App.tsx 2026-02-01 04:24:10 +01:00
485e8b4ed8 Reorder imports in App.tsx for consistency 2026-02-01 04:19:24 +01:00
eecba280a3 Remove unused dependency in package.json 2026-02-01 04:19:24 +01:00
4cc25c1f87 Remove unused CSS import in App.tsx 2026-02-01 04:16:03 +01:00
f4baeeca3d Update keymap configuration in App.tsx 2026-02-01 04:12:15 +01:00
2265ae70b5 Add editorReady state and update dependencies 2026-02-01 04:10:26 +01:00
18945e578e Change font family to inherit in index.css 2026-02-01 04:10:26 +01:00
27ca21094d Prevent default behavior for save shortcut in editor 2026-02-01 04:10:26 +01:00
9671f63828 Refactor editor setup and add tab keymap functionality 2026-02-01 04:09:24 +01:00
2f459a0df4 Hide line numbers and measure in print mode 2026-02-01 04:04:27 +01:00
fd734a7c64 Import CodeMirror styles in App.tsx 2026-02-01 04:03:20 +01:00
7518f032a2 Remove unused CSS for line numbers 2026-02-01 04:02:04 +01:00
3456700ccd Add effect to request editor measure on state changes 2026-02-01 04:01:56 +01:00
f0b7a49611 Change font size in index.css to use CSS variable 2026-02-01 04:01:56 +01:00
9f962afcb5 Update package.json with new dependencies 2026-02-01 04:01:56 +01:00
0413d87a55 Remove src/lib/tln.ts file 2026-02-01 04:01:04 +01:00
aeafaf1d96 Update editor styles for CodeMirror integration 2026-02-01 04:00:57 +01:00
7921384ddf Replace textarea with div for CodeMirror in App.tsx 2026-02-01 04:00:33 +01:00
b843b72670 Update App.tsx to use editorView and compartments for line numbers and editable state 2026-02-01 04:00:21 +01:00
14ed5afe7b Add useEffect to initialize CodeMirror editor in App.tsx 2026-02-01 03:59:56 +01:00
34d8434fb3 Update App.tsx with new refs for editor components 2026-02-01 03:59:56 +01:00
9ecbe6ec65 Update App.tsx with CodeMirror imports and configurations 2026-02-01 03:59:44 +01:00
3ccb7bbe68 Set height to 100% for folder-item in index.css 2026-02-01 03:53:02 +01:00
28809839f2 Use markdownHtml for dangerouslySetInnerHTML in App.tsx 2026-02-01 03:53:02 +01:00
97a3871a8d Add markdownHtml memoization in App.tsx 2026-02-01 03:53:02 +01:00
8c1cf0e6a0 Add useEffect to refresh line numbers in App.tsx 2026-02-01 03:51:30 +01:00
6c528d6fb0 Import refreshLineNumbers in App.tsx 2026-02-01 03:51:30 +01:00
81a7a4fac8 Add refreshLineNumbers function to tln.ts 2026-02-01 03:51:30 +01:00
5b5371d3a8 Add CSS for hidden textarea and line numbers 2026-02-01 03:51:03 +01:00
a1f129a9cb No actual change detected in this commit 2026-02-01 03:51:03 +01:00
44b00e4a13 Import and use appendLineNumbers and removeLineNumbers functions 2026-02-01 03:51:03 +01:00
9dbce4ebee Add line number functionality to text area 2026-02-01 03:51:03 +01:00
18ae1c3d3b Remove onScroll event listener from textarea 2026-02-01 03:51:03 +01:00
15a1b22c91 Remove unused handleTextareaScroll function 2026-02-01 03:51:03 +01:00
afe8a39085 Simplify and optimize App.tsx by removing line numbers and improving markdown preview handling 2026-02-01 03:49:47 +01:00
ddba57d28b Refactor line number handling in App.tsx 2026-02-01 03:49:27 +01:00
10c6043e4e Simplify handleTextareaScroll and remove unused memoized functions in App.tsx 2026-02-01 03:49:09 +01:00
574e7899a5 Remove unused state and ref variables in App.tsx 2026-02-01 03:49:09 +01:00
2d256af21f Remove unused CSS classes in src/index.css 2026-02-01 03:32:32 +01:00
3ed9de1c54 Remove line number measure element from App.tsx 2026-02-01 03:32:27 +01:00
dd288313b9 Simplify line measurement logic in App.tsx 2026-02-01 03:32:27 +01:00
699b55b8fd Remove unused measureRef from App.tsx 2026-02-01 03:32:27 +01:00
997c8767e6 Optimize line metrics computation in App.tsx 2026-02-01 03:32:11 +01:00
b5a6b556c9 Update visible range calculation in App.tsx 2026-02-01 03:32:11 +01:00
51e8fea77b Refactor line calculation logic in App.tsx 2026-02-01 03:31:21 +01:00
7e170dfe85 Update refs and remove unused variables in App.tsx 2026-02-01 03:31:21 +01:00
78758331bb Remove unused FenwickTree class from src/App.tsx 2026-02-01 03:30:53 +01:00
2ea212fd53 Add relative positioning and adjust line numbers in index.css 2026-02-01 03:29:02 +01:00
b907855513 Refactor line number rendering in App.tsx 2026-02-01 03:28:53 +01:00
0024acdf40 Update line height calculation and management in App.tsx 2026-02-01 03:28:40 +01:00
9936fd2c0f Optimize scroll handling in App.tsx 2026-02-01 03:28:09 +01:00
1c9af949b4 Implement line number calculation and visibility tracking in App.tsx 2026-02-01 03:28:05 +01:00
3a2569daad Add state variables for line height and visible range in App.tsx 2026-02-01 03:28:05 +01:00
28edef621c Add FenwickTree class to src/App.tsx 2026-02-01 03:27:23 +01:00
e1e223ed55 Update README.md with improved feature descriptions 2026-02-01 03:19:51 +01:00
ec5cf906d2 Simplify and update README.md features section 2026-02-01 03:18:23 +01:00
016c2a3d4e Update README.md with new folder deletion behavior 2026-02-01 03:18:23 +01:00
6701d7fed9 Swap functionality of new folder and open text buttons in App.tsx 2026-02-01 03:15:19 +01:00
5b93cf2916 Replace icon glyphs with images in App.tsx 2026-02-01 03:14:09 +01:00
a7d69afc8b Update icon imports and add new icons in App.tsx 2026-02-01 03:13:27 +01:00
03621a0f91 Rename new_file_b.png to new_text_b.png in icons directory 2026-02-01 03:13:00 +01:00
db0c456543 Rename settins.png to settings.png 2026-02-01 03:11:50 +01:00
de0ad07fc9 Rename open.png to open_file.png 2026-02-01 03:11:50 +01:00
891cf63261 Remove Unbenannt-1.png icon 2026-02-01 03:11:50 +01:00
3ed4c36ee9 Update new_file_b.png icon 2026-02-01 03:11:50 +01:00
c8545e9670 Add Unbenannt-1.png icon 2026-02-01 03:11:50 +01:00
dc1a515a2a Update open_file_b.png icon 2026-02-01 03:11:50 +01:00
5f16ebe51e Add sidebar expand icon 2026-02-01 03:10:25 +01:00
3466e1901b Add sidebar collapse icon 2026-02-01 03:10:25 +01:00
733378d10f Add settings icon 2026-02-01 03:10:25 +01:00
0863282fdf Add new icons for file operations 2026-02-01 03:10:25 +01:00
02449dd491 Add new_folder icon 2026-02-01 03:09:13 +01:00
bc5dd85b3a Add new_text icon 2026-02-01 03:09:13 +01:00
5bbe9f65b7 Add open icon 2026-02-01 03:09:13 +01:00
b2c139fe18 Add settins icon 2026-02-01 03:09:13 +01:00
31e0f19487 Add sb_collapse icon 2026-02-01 03:09:13 +01:00
9a26725c30 Add sb_expand icon 2026-02-01 03:09:13 +01:00
5c82bb9f89 Add new text button and expand sidebar functionality in App.tsx 2026-02-01 02:53:48 +01:00
379ea3b653 Simplify sidebar layout in index.css 2026-02-01 02:53:48 +01:00
3ef4b7e514 Refactor sidebar layout and button functionality in App.tsx 2026-02-01 02:53:29 +01:00
2d39935045 Update grid layout and remove unused CSS 2026-02-01 02:50:19 +01:00
25561dd7f8 Refactor sidebar layout and button handling in App.tsx 2026-02-01 02:50:16 +01:00
f75fdf8e41 Update CSS for workspace layout and footer styling 2026-02-01 02:47:10 +01:00
10812ee712 Adjust padding in index.css for layout consistency 2026-02-01 02:47:03 +01:00
fe9910a220 Reorganize workspace layout and add new text button styling 2026-02-01 02:46:39 +01:00
1fcef8864f Adjust sidebar footer alignment and padding 2026-02-01 02:43:48 +01:00
ecf860c3a7 Update sidebar collapse button styling and structure 2026-02-01 02:43:48 +01:00
23af6343ca Update sidebar footer layout and button styles 2026-02-01 02:40:54 +01:00
85ed81de10 Refactor sidebar buttons in App.tsx 2026-02-01 02:40:52 +01:00
bc350b5fe9 Adjust top position of close button in index.css 2026-02-01 02:11:20 +01:00
1d2f45af5b Move folder expansion logic to moveTextToFolder callback 2026-02-01 02:08:44 +01:00
c66fee4541 Ensure folder children are only displayed if they exist 2026-02-01 02:08:41 +01:00
30d03e20c1 Remove unused CSS for folder item hover effect 2026-02-01 02:07:11 +01:00
9b88e9306c Remove transition effects in index.css 2026-02-01 02:05:54 +01:00
0b0d3daddc Update border color in index.css 2026-02-01 02:05:54 +01:00
3d1857d290 Further adjust folder item transformation 2026-02-01 02:03:39 +01:00
7a8324b22f Adjust folder item transformation and icon dimensions 2026-02-01 02:03:39 +01:00
0b140b19b7 Increase font size for folder item text 2026-02-01 02:02:16 +01:00
3a48a0a38d Adjust margin for folder item text 2026-02-01 02:02:16 +01:00
d8db15f958 Reduce size of folder item toggle 2026-02-01 02:02:16 +01:00
13b10dccef Adjust gap in folder item lead 2026-02-01 02:00:39 +01:00
d7b2d19759 Update gap in folder item and add lead class 2026-02-01 02:00:39 +01:00
dee929998c Wrap folder toggle and icon in a div 2026-02-01 02:00:22 +01:00
7c6e14a71b Add margin-left to .folder-item 2026-02-01 02:00:22 +01:00
975190efd0 Remove unused styles and add folder item icon styling 2026-02-01 01:58:57 +01:00
e4aa205ab2 Add folder icon and update component structure 2026-02-01 01:58:57 +01:00
12088163dc Update folder rendering to handle entries by parent 2026-02-01 01:55:58 +01:00
d78911d17e Refactor sidebar entry types and sorting logic in App.tsx 2026-02-01 01:55:54 +01:00
4d61e4d5cc Update createText function call to remove sortOrder parameter 2026-02-01 01:51:56 +01:00
3970a30f59 Remove unused parameters in move functions 2026-02-01 01:51:56 +01:00
4baeb10e6d Remove unused variables in App.tsx 2026-02-01 01:50:46 +01:00
bdb67d4093 Remove unused functions getNextTextSortOrder and getNextFolderSortOrder in src/App.tsx 2026-02-01 01:50:39 +01:00
45d7cd940d Simplify sorting logic in App.tsx 2026-02-01 01:50:32 +01:00
9e07db5a2d Remove sort_order columns from db schema and related functions 2026-02-01 01:49:55 +01:00
710eb65f46 Add folder_b icon to src-tauri/icons 2026-02-01 01:42:43 +01:00
0e8085a98b Add folder icon to src-tauri/icons 2026-02-01 01:42:43 +01:00
196cdd1764 Update folder and prompt deletion to include timestamp updates 2026-02-01 01:40:19 +01:00
efdb3cc710 Add CSS for first child folder item positioning 2026-02-01 01:39:16 +01:00
4ae3a0282e Add dark theme support for prompt and folder items in index.css 2026-02-01 01:38:11 +01:00
c31bb9ec65 Update border and line styles in index.css 2026-02-01 01:36:41 +01:00
80654dc515 Refactor folder item styling in index.css 2026-02-01 01:33:04 +01:00
72715d0b9c Refactor folder and prompt item styling in index.css 2026-02-01 01:32:25 +01:00
d7bc9a4358 Adjust left position in folder children styling 2026-02-01 01:32:25 +01:00
077f9df03e Adjust left position of folder children pseudo-elements 2026-02-01 01:29:46 +01:00
5a92648a7d Update tree line styles in index.css 2026-02-01 01:29:46 +01:00
5fa861b315 Add --tree-line variable to CSS 2026-02-01 01:29:46 +01:00
7a7ab7f067 Update folder children styling 2026-02-01 01:27:19 +01:00
4da9032467 Increase border width and height in index.css 2026-02-01 01:26:36 +01:00
14fe9f9f2b Add folder children separator and adjust padding 2026-02-01 01:16:53 +01:00
663d7750ba Adjust padding and dimensions in folder item styling 2026-02-01 01:15:34 +01:00
1d9c1246dd Update folder item styles and add delete button 2026-02-01 01:09:58 +01:00
4ae8f6dfc2 Add delete button for folder items 2026-02-01 01:09:52 +01:00
bc68d4b661 Add folder move functionality and update context menu 2026-02-01 01:06:51 +01:00
58184c0f51 Remove unused parameter in renderTextItem function 2026-02-01 01:01:08 +01:00
33177fa035 Remove drag-and-drop functionality from App.tsx 2026-02-01 00:59:57 +01:00
074f27b835 Remove unused drag-and-drop functionality from App.tsx 2026-02-01 00:59:53 +01:00
8ad329c9cd Update dependency array in App.tsx 2026-01-31 19:12:23 +01:00
e3dc293f81 Remove unused function call in App.tsx 2026-01-31 19:12:23 +01:00
00613f60d5 Add input styles for prompt-item and folder-item 2026-01-31 19:10:39 +01:00
cd512e8456 Refactor and reorder functions in App.tsx 2026-01-31 19:10:28 +01:00
14203c33a1 Add folder and text editing functionality 2026-01-31 19:10:16 +01:00
f4120dfab3 Add state for editing folder and text in App.tsx 2026-01-31 19:05:37 +01:00
69f9b41708 Update README.md to include folder collapse state in UI settings description 2026-01-31 18:57:55 +01:00
a19c167769 Update README with new features 2026-01-31 18:56:48 +01:00
892e060bc1 Add no matching texts message for search results 2026-01-31 18:56:48 +01:00
7d758364e6 Update sort order logic to consider search state 2026-01-31 18:56:48 +01:00
15e61635a9 Refactor title update logic in App.tsx 2026-01-31 18:54:16 +01:00
131ef453a6 Add missing index for prompts folder in database migration 2026-01-31 18:54:10 +01:00
296e0242a1 Remove depth parameter from renderTextItem and renderFolder functions, update CSS for folder styling 2026-01-31 18:53:00 +01:00
3042a8e92d auto-git:
[change] src/App.tsx
2026-01-31 18:52:42 +01:00
a0a9e2360b Add renderTextItem and renderFolder functions in App.tsx 2026-01-31 18:50:12 +01:00
988e2ed55a auto-git:
[change] src/App.tsx
2026-01-31 18:49:58 +01:00
2297cab6a7 Add drag and drop functionality for texts and folders in App.tsx 2026-01-31 18:47:40 +01:00
7fae29bb71 Add folder path building and text/folder handling functions in App.tsx 2026-01-31 18:47:30 +01:00
e0e5a20d4d Add functionality to create new folders 2026-01-31 18:46:43 +01:00
96ef5f40f6 Update createText calls to include sortOrder parameter 2026-01-31 18:46:34 +01:00
197e71304a Add folder and text sorting logic in App.tsx 2026-01-31 18:46:25 +01:00
1f044ea362 Add folder and text sorting logic 2026-01-31 18:46:17 +01:00
361621a059 Add effect to refresh folders on component mount 2026-01-31 18:46:17 +01:00
e58c21bb38 Add refreshFolders function in App.tsx 2026-01-31 18:45:25 +01:00
d4497751fe Persist expandedFolders in local storage 2026-01-31 18:45:20 +01:00
1921b63ffc Add dragItemRef to App.tsx 2026-01-31 18:45:20 +01:00
354c974bd8 Add folder and expandedFolders state management in App.tsx 2026-01-31 18:45:12 +01:00
16a9dab119 Import Menu from @tauri-apps/api/menu in App.tsx 2026-01-31 18:45:12 +01:00
67dcb3f9e4 Add folder management functions and types in App.tsx 2026-01-31 18:44:53 +01:00
740af2021c Add folder and sort order functionality to database schema and operations 2026-01-31 18:44:22 +01:00
26 changed files with 3055 additions and 565 deletions

2
.gitignore vendored
View File

@@ -11,5 +11,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
src-tauri/target/
src-tauri/gen/Cargo.lock
src-tauri/gen/
*.lock

View File

@@ -1,23 +1,19 @@
# TextDB
TextDB is a **non-destructive**, completely offline, local-first text editor with built-in Markdown preview functionality. Its core promise is simple: your edits never overwrite your history. Every manual save is immutable and append-only, and autosaves are kept as a separate draft layer. No accounts, no telemetry, no remote fonts, and no network calls.
TextDB is a **non-destructive**, completely offline, local-first text editor with built-in Markdown preview and local AI writing tools. Its core promise is simple: your edits never overwrite your history. Every manual save is immutable and append-only, and autosaves are kept as a separate draft layer. With a local Ollama model (recommended: `ministral:3b`), it can convert plain text to Markdown, proofread, summarize, translate, rewrite, change style, or run a custom instruction against a whole text or a selected passage. No accounts, no telemetry, no remote fonts, and no outside network calls.
## Features
- Non-destructive editing: manual saves are immutable; drafts never overwrite manual versions.
- Sidebar list of texts with live search (titles + version bodies).
- Editor with title, content area, and clear save status.
- Cmd/Ctrl+S creates a new immutable manual version.
- Markdown preview mode with external link support and printable output.
- Pressing Tab toggles Edit/Preview mode.
- Autosave draft (debounced) that can be safely discarded.
- History panel listing versions by timestamp (drafts included when present).
- Settings: theme (default/bright), text size slider, and optional line numbers.
- Collapsible sidebar with persistent state.
- Export the current text to a `.txt` file.
- Open `.txt` or `.md` files to create new entries from file content.
- Drag and drop `.txt` or `.md` files onto the window to import them.
- Registers `.txt` and `.md` file associations for using TextDB as a default editor.
- Non-destructive editing: immutable manual saves + safe autosaved drafts.
- Version history panel with quick restore.
- Markdown preview mode with split view, printable output, clickable links, and automatic raw URL detection.
- Local AI tools via Ollama for Markdown conversion, proofreading, summarizing, translation, rewriting, style changes, and custom prompts.
- AI edits can run on the whole text or just the selected part, and results are written back as drafts.
- In-document search with Cmd/Ctrl+F, result highlighting, and next / previous navigation.
- Live document stats in the footer: characters, words, sentences, and estimated tokens.
- Sort your texts in nested folders with expandable tree; move items around via right-click.
- Open/import text files from the file picker or system “Open With”; export to `.txt`, export the database, or print / `.pdf`.
- Settings for theme, text size, line numbers, split view, Ollama connection, and editable AI prompt templates.
## Use cases
@@ -26,6 +22,7 @@ TextDB is a **non-destructive**, completely offline, local-first text editor wit
- **Research notes**: maintain evolving notes with an audit trail of changes.
- **Sensitive drafts**: keep local-only writing with immutable history and zero cloud sync.
- **Prompt or snippet library**: store and iterate on reusable text safely.
- **Offline writing assistant**: run local AI edits without sending text to external services.
## Tech stack
@@ -47,10 +44,13 @@ npm install
npm run tauri dev
```
For local AI tools, install Ollama: `https://ollama.com`
## Autosave + versioning behavior
- Typing triggers a debounced autosave (~600ms) that writes to the draft record.
- Manual saves (Cmd/Ctrl+S or the button) append a new version and clear any draft.
- AI edits preserve the current text state first when needed, then write the AI result back as a new draft.
- Autosave only overwrites the single draft row; manual versions are never overwritten.
- History shows all manual versions plus the current draft (if any).
@@ -59,4 +59,4 @@ npm run tauri dev
- SQLite is loaded via `Database.load("sqlite:text.db")`.
- The database file is stored in the Tauri app data directory.
- The app is fully offline by design: no telemetry, no external fonts, no CDNs.
- UI settings (theme, text size, line numbers, sidebar state, last selected text) are stored in localStorage.
- UI settings (theme, text size, line numbers, split view, sidebar + folder collapse state, last selected text, and AI prompt templates) are stored in localStorage.

View File

@@ -10,9 +10,13 @@
"tauri": "tauri"
},
"dependencies": {
"@codemirror/commands": "^6.10.1",
"@codemirror/search": "^6.6.0",
"@codemirror/state": "^6.5.4",
"@codemirror/view": "^6.39.12",
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-fs": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0",
"@tauri-apps/plugin-sql": "^2.0.0",

142
src-tauri/Cargo.lock generated
View File

@@ -1574,9 +1574,9 @@ dependencies = [
[[package]]
name = "ico"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [
"byteorder",
"png 0.17.16",
@@ -2244,38 +2244,9 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.10.0",
"block2",
"libc",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-text",
"objc2-core-video",
"objc2-foundation",
"objc2-quartz-core",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-foundation",
]
@@ -2303,41 +2274,6 @@ dependencies = [
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-core-video"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-io-surface",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
@@ -2361,7 +2297,6 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.10.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
@@ -2377,16 +2312,6 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-javascript-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586"
dependencies = [
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@@ -2399,17 +2324,6 @@ dependencies = [
"objc2-foundation",
]
[[package]]
name = "objc2-security"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
@@ -2434,8 +2348,6 @@ dependencies = [
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"objc2-javascript-core",
"objc2-security",
]
[[package]]
@@ -3074,9 +2986,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.12.28"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -3093,7 +3005,6 @@ dependencies = [
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-util",
@@ -4010,9 +3921,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.9.5"
version = "2.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033"
checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d"
dependencies = [
"anyhow",
"bytes",
@@ -4061,9 +3972,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.5.3"
version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d"
dependencies = [
"anyhow",
"cargo_toml",
@@ -4083,9 +3994,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.5.2"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf"
checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -4110,9 +4021,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.5.2"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde"
checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -4236,9 +4147,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.9.2"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892"
checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2"
dependencies = [
"cookie",
"dpi",
@@ -4261,9 +4172,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.9.3"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
dependencies = [
"gtk",
"http",
@@ -4271,7 +4182,6 @@ dependencies = [
"log",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"once_cell",
"percent-encoding",
"raw-window-handle",
@@ -4288,9 +4198,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.8.1"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490"
checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d"
dependencies = [
"anyhow",
"brotli",
@@ -5001,9 +4911,9 @@ dependencies = [
[[package]]
name = "wasm-streams"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
dependencies = [
"futures-util",
"js-sys",
@@ -5094,9 +5004,9 @@ dependencies = [
[[package]]
name = "webkit2gtk"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@@ -5118,9 +5028,9 @@ dependencies = [
[[package]]
name = "webkit2gtk-sys"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
dependencies = [
"bitflags 1.3.2",
"cairo-sys-rs",
@@ -5739,9 +5649,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "wry"
version = "0.53.5"
version = "0.54.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2"
checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a"
dependencies = [
"base64 0.22.1",
"block2",

View File

@@ -5,12 +5,12 @@ description = "TextDB"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2", features = [] }
tauri-build = { version = "2.5", features = [] }
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri = { version = "2", features = [] }
tauri = { version = "2.10", features = [] }
tauri-plugin-clipboard-manager = { version = "2" }
tauri-plugin-dialog = { version = "2" }
tauri-plugin-fs = { version = "2" }

BIN
src-tauri/icons/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -13,10 +13,7 @@ fn take_pending_opens(state: tauri::State<PendingOpens>) -> Vec<String> {
fn main() {
tauri::Builder::default()
.setup(|app| {
app.manage(PendingOpens(Mutex::new(Vec::new())));
Ok(())
})
.manage(PendingOpens(Mutex::new(Vec::new())))
.invoke_handler(tauri::generate_handler![take_pending_opens])
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_clipboard_manager::init())
@@ -36,9 +33,10 @@ fn main() {
if paths.is_empty() {
return;
}
let state = app.state::<PendingOpens>();
let mut pending = state.0.lock().expect("pending opens lock");
pending.extend(paths.iter().cloned());
if let Some(state) = app.try_state::<PendingOpens>() {
let mut pending = state.0.lock().expect("pending opens lock");
pending.extend(paths.iter().cloned());
}
let _ = app.emit("file-opened", paths);
}
})

View File

@@ -63,7 +63,8 @@
},
{
"ext": [
"md"
"md",
"markdown"
],
"name": "Markdown Document",
"description": "Markdown document",
@@ -71,8 +72,7 @@
"role": "Editor",
"rank": "Default",
"contentTypes": [
"net.daringfireball.markdown",
"public.plain-text"
"net.daringfireball.markdown"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
--bg-active: #202020;
--border-strong: #555;
--danger-hover: #4f4f4f;
--tree-line: #5f5f5f;
}
body[data-theme="light"] {
@@ -41,6 +42,7 @@ body[data-theme="light"] {
--bg-active: #e6e6e6;
--border-strong: #b5b5b5;
--danger-hover: #d6d6d6;
--tree-line: #9a9a9a;
}
@@ -77,7 +79,7 @@ body {
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px 0;
padding: 24px 0 16px;
background: var(--panel);
border-right: 1px solid var(--border);
height: 100vh;
@@ -107,15 +109,17 @@ body {
.sidebar__footer {
display: flex;
flex-direction: column;
gap: 12px;
flex-direction: row;
align-items: center;
gap: 8px;
justify-content: center;
margin-top: auto;
padding-left: 24px;
padding-right: 24px;
padding: 0 24px;
}
.sidebar__footer .button {
width: 100%;
.sidebar__footer .icon-button {
width: 36px;
height: 36px;
}
.app-title {
@@ -131,6 +135,7 @@ body {
border: 1px solid var(--border);
background: var(--bg-elevated);
color: var(--ink);
caret-color: var(--ink);
font-size: 0.95rem;
}
@@ -228,6 +233,17 @@ body {
flex: 1;
}
.prompt-item__input {
width: 100%;
font: inherit;
color: var(--ink);
background: var(--bg-input);
caret-color: var(--ink);
border: 1px solid var(--border);
border-radius: 10px;
padding: 6px 10px;
}
.prompt-item__delete {
width: 34px;
height: 34px;
@@ -242,7 +258,7 @@ body {
justify-content: center;
cursor: pointer;
position: absolute;
top: 8px;
top: 4px;
right: 8px;
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
@@ -259,16 +275,157 @@ body {
word-break: break-word;
}
.prompt-item__meta {
font-size: 0.8rem;
.folder-node {
display: flex;
flex-direction: column;
gap: 8px;
border: 1px solid transparent;
}
.folder-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 14px;
border: 1px solid var(--tree-line);
background: var(--bg-elevated);
color: var(--ink);
cursor: pointer;
transition: none;
position: relative;
}
body:not([data-theme="light"]) .prompt-item,
body:not([data-theme="light"]) .folder-item {
background: var(--bg-contrast);
}
.folder-item__toggle {
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--muted);
cursor: pointer;
padding: 0;
margin-left: -4px;
font-size: 1.6rem;
transform: translateY(-3px);
}
.folder-item__lead {
display: inline-flex;
align-items: center;
gap: 1px;
}
.folder-item__icon {
width: 1em;
height: 1em;
flex: 0 0 1em;
display: block;
}
.folder-item__title {
font-weight: 600;
flex: 1;
overflow-wrap: anywhere;
word-break: break-word;
}
.folder-item__delete {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid transparent;
background: transparent;
color: var(--muted);
font-size: 1rem;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 6px;
}
.folder-item__delete:hover {
color: var(--ink);
}
.folder-item__input {
width: 100%;
font: inherit;
color: var(--ink);
background: var(--bg-input);
caret-color: var(--ink);
border: 1px solid var(--border);
border-radius: 10px;
padding: 6px 10px;
}
.folder-children {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 6px;
padding-left: 18px;
position: relative;
}
.folder-children > * {
position: relative;
}
.folder-children > *::after {
content: "";
position: absolute;
left: -12px;
top: -10px;
bottom: -10px;
width: 1px;
background: var(--tree-line);
}
.folder-children > :first-child::after {
top: -16px;
}
.folder-children > :last-child::after {
bottom: 50%;
}
.folder-children .folder-item::before,
.folder-children .prompt-item::before {
content: "";
position: absolute;
left: -12px;
top: 50%;
width: 12px;
height: 1px;
background: var(--tree-line);
transform: translateY(-50%);
}
.workspace {
padding: 28px 32px;
padding: 16px 32px;
height: 100%;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
}
.workspace__body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.workspace__topbar {
@@ -292,7 +449,7 @@ body {
display: grid;
grid-template-columns: 1fr;
gap: 24px;
height: 100%;
flex: 1;
min-height: 0;
}
@@ -335,6 +492,7 @@ body {
padding: 6px 2px 8px;
background: transparent;
color: var(--ink);
caret-color: var(--ink);
}
.title-input:focus {
@@ -350,6 +508,11 @@ body {
color: var(--muted);
}
.status-line--secondary {
margin-top: 4px;
font-size: 0.82rem;
}
.icon-button {
display: inline-flex;
align-items: center;
@@ -437,67 +600,166 @@ body {
border-color: transparent;
}
body[data-theme="light"] .editor__textarea-wrap--preview {
background: var(--bg);
}
body:not([data-theme="light"]) .editor__textarea-wrap--preview {
background: var(--bg);
}
.editor__textarea-wrap--split {
flex-direction: row;
}
body[data-theme="light"] .editor__textarea-wrap--split {
background: var(--bg);
}
body:not([data-theme="light"]) .editor__textarea-wrap--split {
background: var(--bg);
}
.editor__textarea-wrap--split .editor__codemirror {
flex: 1;
border-right: 1px solid var(--border);
}
.editor__textarea-wrap--split .markdown-preview {
flex: 1;
}
body[data-theme="light"] .editor__textarea-wrap--split .markdown-preview {
background: var(--bg);
}
body:not([data-theme="light"]) .editor__textarea-wrap--split .markdown-preview {
background: var(--bg);
}
.editor__textarea-wrap:focus-within {
border-color: rgba(255, 255, 255, 0.18);
}
.line-numbers {
padding: 14px 10px;
min-width: 3ch;
text-align: right;
font-family: "SF Mono", "Menlo", "Consolas", monospace;
font-size: 0.95rem;
line-height: 1.5;
color: var(--muted);
background: var(--bg-contrast);
border-right: 1px solid var(--border);
user-select: none;
overflow: hidden;
}
.line-numbers__line {
height: 1.5em;
display: flex;
align-items: flex-start;
}
.line-measure {
position: absolute;
top: 0;
left: 0;
visibility: hidden;
pointer-events: none;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
font-family: "SF Mono", "Menlo", "Consolas", monospace;
font-size: 0.95rem;
line-height: 1.5;
padding: 14px;
}
.line-measure__line {
white-space: pre-wrap;
}
.editor__textarea {
.editor__codemirror {
flex: 1;
width: 100%;
resize: none;
border: none;
padding: 14px;
font-family: "SF Mono", "Menlo", "Consolas", monospace;
font-size: 0.95rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
min-height: 0;
}
.editor__codemirror--hidden {
display: none;
}
.editor__codemirror .cm-editor {
height: 100%;
background: transparent;
color: var(--ink);
font-family: inherit;
font-size: var(--base-font-size);
line-height: 1.5;
}
.editor__codemirror .cm-editor.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor,
.editor__codemirror .cm-dropCursor {
border-left-color: #f5f5f5 !important;
border-left-width: 1.6px !important;
}
.editor__codemirror .cm-fatCursor {
background: rgba(245, 245, 245, 0.45);
}
.editor__codemirror .cm-scroller {
overflow: auto;
}
.editor__codemirror .cm-content {
padding: 14px;
}
.editor__codemirror .cm-gutters {
background: var(--bg-contrast);
border-right: 1px solid var(--border);
color: var(--muted);
}
.editor__codemirror .cm-gutterElement {
padding: 0 10px 0 0;
}
.editor__codemirror .cm-activeLineGutter {
background: transparent;
}
.editor__codemirror .cm-panels {
background: var(--bg-elevated);
color: var(--ink);
border-bottom: 1px solid var(--border);
}
.editor__codemirror .cm-panels-top {
border-bottom: 1px solid var(--border);
}
.editor__codemirror .cm-search {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
padding: 10px 12px;
}
.editor__codemirror .cm-search label {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--muted);
}
.editor__codemirror .cm-search input,
.editor__codemirror .cm-search button {
font: inherit;
}
.editor__codemirror .cm-search input[type="text"] {
min-width: 220px;
padding: 8px 10px;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--bg-input);
color: var(--ink);
}
.editor__textarea:focus {
outline: none;
.editor__codemirror .cm-search button {
border-radius: 10px;
border: 1px solid var(--border);
padding: 8px 10px;
background: var(--bg-input);
color: var(--ink);
cursor: pointer;
}
.editor__codemirror .cm-search button:hover {
border-color: var(--accent-strong);
}
.editor__codemirror .cm-search [name="close"] {
padding-left: 12px;
padding-right: 12px;
}
.editor__codemirror .cm-search input[type="checkbox"] {
accent-color: var(--ink);
}
.editor__codemirror .cm-searchMatch {
background: rgba(214, 214, 96, 0.28);
outline: 1px solid rgba(214, 214, 96, 0.38);
}
.editor__codemirror .cm-searchMatch.cm-searchMatch-selected {
background: rgba(233, 216, 112, 0.42);
}
.markdown-preview {
@@ -508,6 +770,18 @@ body {
color: var(--ink);
}
body[data-theme="light"] .markdown-preview {
background: var(--bg);
}
body:not([data-theme="light"]) .markdown-preview {
background: var(--bg);
}
.markdown-preview--hidden {
display: none;
}
.editor__footer {
display: flex;
flex-wrap: wrap;
@@ -517,6 +791,19 @@ body {
.editor__footer-status {
margin-left: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.editor__stats {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 10px;
font-size: 0.82rem;
color: var(--muted);
}
.banner {
@@ -676,6 +963,10 @@ body {
gap: 12px;
}
.modal__card--wide {
width: min(560px, 92vw);
}
.modal__title {
font-size: 1.2rem;
}
@@ -684,6 +975,37 @@ body {
color: var(--muted);
}
.modal__textarea {
width: 100%;
min-height: 170px;
resize: vertical;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--bg-input);
color: var(--ink);
caret-color: var(--ink);
padding: 12px 14px;
font: inherit;
}
.modal__hint {
font-size: 0.8rem;
color: var(--muted);
}
.modal__selection-preview {
max-height: 180px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--bg-input);
color: var(--ink);
padding: 12px 14px;
font-size: 0.9rem;
}
.modal__actions {
display: flex;
justify-content: flex-end;
@@ -774,6 +1096,8 @@ body {
inset: 0;
display: grid;
place-items: center;
padding: 16px;
overflow: auto;
z-index: 30;
}
@@ -786,7 +1110,8 @@ body {
.settings-panel {
position: relative;
z-index: 1;
width: min(420px, 92vw);
width: min(560px, calc(100vw - 32px));
max-height: calc(100vh - 32px);
padding: 20px;
border-radius: 16px;
background: var(--bg-elevated);
@@ -795,6 +1120,7 @@ body {
display: flex;
flex-direction: column;
gap: 16px;
overflow: hidden;
}
.settings-panel__header {
@@ -807,17 +1133,55 @@ body {
font-size: 1.1rem;
}
.settings-panel__body {
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
overflow-y: auto;
padding-right: 4px;
margin-right: -4px;
}
.settings-panel__section {
display: flex;
flex-direction: column;
gap: 8px;
}
.settings-panel__section--flush {
gap: 10px;
}
.settings-panel__section-title {
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
margin-top: 4px;
}
.settings-panel__label {
font-size: 0.9rem;
color: var(--muted);
}
.settings-panel__input,
.settings-panel__textarea {
border-radius: 10px;
border: 1px solid var(--border);
background: var(--bg-input);
color: var(--ink);
caret-color: var(--ink);
padding: 8px 10px;
font-size: 0.95rem;
}
.settings-panel__textarea {
resize: vertical;
min-height: 120px;
}
.settings-panel__select {
border-radius: 10px;
border: 1px solid var(--border);
@@ -844,10 +1208,87 @@ body {
font-size: 0.9rem;
}
.settings-panel__hint {
font-size: 0.8rem;
color: var(--muted);
}
.settings-panel__status {
font-size: 0.8rem;
word-break: break-word;
}
.settings-panel__status--success {
color: var(--ink);
}
.settings-panel__status--error {
color: #d36b6b;
}
.prompt-accordion {
border: 1px solid var(--border);
border-radius: 12px;
background: var(--bg-input);
overflow: hidden;
}
.prompt-accordion__toggle {
width: 100%;
border: none;
background: transparent;
color: var(--ink);
padding: 12px 14px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
font: inherit;
text-align: left;
cursor: pointer;
}
.prompt-accordion__indicator {
color: var(--muted);
font-size: 1.1rem;
line-height: 1;
}
.prompt-accordion__body {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 14px 14px;
}
.prompt-accordion__actions {
display: flex;
justify-content: flex-end;
}
body[data-theme="light"] .editor__textarea-wrap:focus-within {
border-color: var(--accent-strong);
}
body[data-theme="light"] .editor__codemirror .cm-editor.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor,
body[data-theme="light"] .editor__codemirror .cm-dropCursor {
border-left-color: #1f1f1f !important;
border-left-width: 1.6px !important;
}
body[data-theme="light"] .editor__codemirror .cm-fatCursor {
background: rgba(31, 31, 31, 0.28);
}
body[data-theme="light"] .editor__codemirror .cm-searchMatch {
background: rgba(219, 199, 81, 0.3);
outline-color: rgba(170, 141, 33, 0.28);
}
body[data-theme="light"] .editor__codemirror .cm-searchMatch.cm-searchMatch-selected {
background: rgba(219, 199, 81, 0.44);
}
.settings-panel__section--row {
flex-direction: row;
align-items: center;
@@ -881,9 +1322,7 @@ body[data-theme="light"] .editor__textarea-wrap:focus-within {
body.print-markdown .sidebar,
body.print-markdown .history,
body.print-markdown .editor__header,
body.print-markdown .editor__footer,
body.print-markdown .line-numbers,
body.print-markdown .line-measure {
body.print-markdown .editor__footer {
display: none !important;
}
@@ -898,6 +1337,14 @@ body[data-theme="light"] .editor__textarea-wrap:focus-within {
overflow: visible;
}
body.print-markdown .editor__codemirror {
display: none !important;
}
body.print-markdown .editor__textarea-wrap--split {
display: block;
}
body.print-markdown .markdown-preview {
padding: 32px 36px;
overflow: visible;

View File

@@ -6,6 +6,15 @@ export type Text = {
created_at: number;
updated_at: number;
last_saved_version_id: string | null;
folder_id: string | null;
};
export type Folder = {
id: string;
name: string;
parent_id: string | null;
created_at: number;
updated_at: number;
};
export type TextVersion = {
@@ -26,12 +35,21 @@ export type TextDraft = {
const MIGRATIONS = [
"PRAGMA foreign_keys = ON;",
`CREATE TABLE IF NOT EXISTS folders(
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY(parent_id) REFERENCES folders(id)
);`,
`CREATE TABLE IF NOT EXISTS prompts(
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
last_saved_version_id TEXT
last_saved_version_id TEXT,
folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL
);`,
`CREATE TABLE IF NOT EXISTS prompt_versions(
id TEXT PRIMARY KEY,
@@ -50,7 +68,9 @@ const MIGRATIONS = [
FOREIGN KEY(prompt_id) REFERENCES prompts(id) ON DELETE CASCADE
);`,
"CREATE INDEX IF NOT EXISTS idx_prompt_versions_prompt_time ON prompt_versions(prompt_id, created_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_prompts_updated ON prompts(updated_at DESC);"
"CREATE INDEX IF NOT EXISTS idx_prompts_updated ON prompts(updated_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id);",
"CREATE INDEX IF NOT EXISTS idx_folders_updated ON folders(updated_at DESC);"
];
let dbPromise: Promise<Database> | null = null;
@@ -59,6 +79,91 @@ async function migrate(db: Database) {
for (const statement of MIGRATIONS) {
await db.execute(statement);
}
await ensureColumn(db, "prompts", "folder_id", "TEXT REFERENCES folders(id) ON DELETE SET NULL");
await db.execute("CREATE INDEX IF NOT EXISTS idx_prompts_folder ON prompts(folder_id);");
await dropSortOrderColumns(db);
}
async function ensureColumn(
db: Database,
table: string,
column: string,
definition: string
) {
const columns = await db.select<{ name: string }[]>(
`PRAGMA table_info(${table})`
);
if (columns.some((col) => col.name === column)) return;
await db.execute(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
}
async function hasColumn(db: Database, table: string, column: string) {
const columns = await db.select<{ name: string }[]>(
`PRAGMA table_info(${table})`
);
return columns.some((col) => col.name === column);
}
async function dropSortOrderColumns(db: Database) {
const promptsHasSort = await hasColumn(db, "prompts", "sort_order");
const foldersHasSort = await hasColumn(db, "folders", "sort_order");
if (!promptsHasSort && !foldersHasSort) return;
await db.execute("PRAGMA foreign_keys = OFF");
await db.execute("BEGIN TRANSACTION");
try {
if (foldersHasSort) {
await db.execute("DROP TABLE IF EXISTS folders_new");
await db.execute(`
CREATE TABLE folders_new(
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY(parent_id) REFERENCES folders(id)
);
`);
await db.execute(`
INSERT INTO folders_new(id, name, parent_id, created_at, updated_at)
SELECT id, name, parent_id, created_at, updated_at FROM folders
`);
await db.execute("DROP TABLE folders");
await db.execute("ALTER TABLE folders_new RENAME TO folders");
}
if (promptsHasSort) {
await db.execute("DROP TABLE IF EXISTS prompts_new");
await db.execute(`
CREATE TABLE prompts_new(
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
last_saved_version_id TEXT,
folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL
);
`);
await db.execute(`
INSERT INTO prompts_new(id, title, created_at, updated_at, last_saved_version_id, folder_id)
SELECT id, title, created_at, updated_at, last_saved_version_id, folder_id FROM prompts
`);
await db.execute("DROP TABLE prompts");
await db.execute("ALTER TABLE prompts_new RENAME TO prompts");
}
await db.execute("COMMIT");
} catch (error) {
await db.execute("ROLLBACK");
throw error;
} finally {
await db.execute("PRAGMA foreign_keys = ON");
}
await db.execute("CREATE INDEX IF NOT EXISTS idx_prompts_updated ON prompts(updated_at DESC);");
await db.execute("CREATE INDEX IF NOT EXISTS idx_prompts_folder ON prompts(folder_id);");
await db.execute("CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id);");
await db.execute("CREATE INDEX IF NOT EXISTS idx_folders_updated ON folders(updated_at DESC);");
}
export async function getDb(): Promise<Database> {
@@ -79,7 +184,8 @@ export async function initDb() {
export async function listTexts(): Promise<Text[]> {
const db = await getDb();
const rows = await db.select<Text[]>(
"SELECT * FROM prompts ORDER BY updated_at DESC"
`SELECT * FROM prompts
ORDER BY updated_at DESC`
);
return rows;
}
@@ -144,7 +250,8 @@ export async function getDraft(promptId: string): Promise<TextDraft | null> {
export async function createText(
title: string,
body: string
body: string,
folderId: string | null = null
): Promise<{ textId: string; versionId: string; createdAt: number }> {
const db = await getDb();
const now = Date.now();
@@ -152,8 +259,8 @@ export async function createText(
const versionId = crypto.randomUUID();
await db.execute(
"INSERT INTO prompts(id, title, created_at, updated_at, last_saved_version_id) VALUES($1, $2, $3, $4, $5)",
[textId, title, now, now, versionId]
"INSERT INTO prompts(id, title, created_at, updated_at, last_saved_version_id, folder_id) VALUES($1, $2, $3, $4, $5, $6)",
[textId, title, now, now, versionId, folderId]
);
await db.execute(
@@ -166,6 +273,80 @@ export async function createText(
return { textId, versionId, createdAt: now };
}
export async function listFolders(): Promise<Folder[]> {
const db = await getDb();
return db.select<Folder[]>(
`SELECT * FROM folders
ORDER BY updated_at DESC`
);
}
export async function createFolder(
name: string,
parentId: string | null = null
): Promise<{ folderId: string; createdAt: number }> {
const db = await getDb();
const now = Date.now();
const folderId = crypto.randomUUID();
await db.execute(
"INSERT INTO folders(id, name, parent_id, created_at, updated_at) VALUES($1, $2, $3, $4, $5)",
[folderId, name, parentId, now, now]
);
return { folderId, createdAt: now };
}
export async function updateFolderName(folderId: string, name: string) {
const db = await getDb();
const now = Date.now();
await db.execute(
"UPDATE folders SET name = $1, updated_at = $2 WHERE id = $3",
[name, now, folderId]
);
}
export async function moveFolder(
folderId: string,
parentId: string | null
) {
const db = await getDb();
const now = Date.now();
await db.execute(
"UPDATE folders SET parent_id = $1, updated_at = $2 WHERE id = $3",
[parentId, now, folderId]
);
}
export async function deleteFolder(folderId: string) {
const db = await getDb();
const now = Date.now();
const rows = await db.select<{ parent_id: string | null }[]>(
"SELECT parent_id FROM folders WHERE id = $1 LIMIT 1",
[folderId]
);
const parentId = rows[0]?.parent_id ?? null;
await db.execute(
"UPDATE folders SET parent_id = $1, updated_at = $2 WHERE parent_id = $3",
[parentId, now, folderId]
);
await db.execute(
"UPDATE prompts SET folder_id = $1, updated_at = $2 WHERE folder_id = $3",
[parentId, now, folderId]
);
await db.execute("DELETE FROM folders WHERE id = $1", [folderId]);
}
export async function moveTextToFolder(
textId: string,
folderId: string | null
) {
const db = await getDb();
const now = Date.now();
await db.execute(
"UPDATE prompts SET folder_id = $1, updated_at = $2 WHERE id = $3",
[folderId, now, textId]
);
}
export async function saveManualVersion(
promptId: string,
title: string,
@@ -253,3 +434,9 @@ export async function deleteTextVersion(promptId: string, versionId: string) {
[nextId, promptId]
);
}
export async function exportDatabaseSnapshot(destinationPath: string) {
const db = await getDb();
const escapedPath = destinationPath.replace(/'/g, "''");
await db.execute(`VACUUM INTO '${escapedPath}'`);
}

View File

@@ -17,9 +17,14 @@
"Liberation Mono", "Courier New", monospace;
}
body:not([data-theme="light"]) .md-root {
--md-surface-alt: var(--bg);
}
body[data-theme="light"] .md-root {
--md-link-color: #0a66c2;
--md-link-hover-color: #004182;
--md-surface-alt: var(--bg);
--md-code-inline-bg: rgba(0, 0, 0, 0.06);
}

View File

@@ -179,15 +179,55 @@ export function markdownToHTML(text) {
return '';
};
html = html.replace(/\[([^\]]+?)\]\(([^)]+?)\)/g, (_, label, href) => {
const url = safeLink(href);
const tooltip = escapeHtml(href || '');
const renderExternalLink = (label, hrefRaw) => {
const url = safeLink(hrefRaw);
const tooltip = escapeHtml(hrefRaw || '');
if (!url) return label;
return `<a class="md-link md-link--external" href="${escapeAttr(
url
)}" target="_blank" rel="noreferrer noopener"><span class="md-link__label">${label}</span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="md-icon md-icon-external"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg><span class="md-link__tooltip">${tooltip}</span></a>`;
};
html = html.replace(/\[([^\]]+?)\]\(([^)]+?)\)/g, (_, label, href) => {
return renderExternalLink(label, href);
});
const autoLinkPlainUrls = (source) => {
const protectedChunkRe = /(<a\b[\s\S]*?<\/a>|<code>[\s\S]*?<\/code>|@@CODEBLOCK\d+@@)/g;
const isProtectedChunk = /^(<a\b[\s\S]*<\/a>|<code>[\s\S]*<\/code>|@@CODEBLOCK\d+@@)$/;
const trimTrailingPunctuation = (value) => {
let linked = value;
let trailing = '';
while (linked.length > 0) {
const last = linked[linked.length - 1];
if (!/[)\].,!?;:]/.test(last)) break;
if (last === ')') {
const opens = (linked.match(/\(/g) || []).length;
const closes = (linked.match(/\)/g) || []).length;
if (closes <= opens) break;
}
linked = linked.slice(0, -1);
trailing = last + trailing;
}
return { linked, trailing };
};
return source
.split(protectedChunkRe)
.map((part) => {
if (!part || isProtectedChunk.test(part)) return part;
return part.replace(/(^|[\s(>])((?:https?:\/\/|www\.)[^\s<]+)/g, (_, lead, rawUrl) => {
const { linked, trailing } = trimTrailingPunctuation(rawUrl);
if (!linked) return `${lead}${rawUrl}`;
const href = /^www\./i.test(linked) ? `https://${linked}` : linked;
return `${lead}${renderExternalLink(linked, href)}${trailing}`;
});
})
.join('');
};
html = autoLinkPlainUrls(html);
// 6) Convert line-breaks to HTML paragraphs and <br /> inside paragraphs
const linesWithHtml = html.split("\n");
const htmlLines = [];