janito 1.8.1__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +1 -1
- janito/agent/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -3
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +177 -114
- janito/agent/conversation_api.py +179 -159
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/llm_conversation_history.py +70 -0
- janito/agent/openai_client.py +44 -21
- janito/agent/openai_schema_generator.py +164 -128
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +9 -8
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +63 -50
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +42 -5
- janito/agent/tools/__init__.py +13 -12
- janito/agent/tools/create_directory.py +9 -6
- janito/agent/tools/create_file.py +35 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +50 -5
- janito/agent/tools/find_files.py +40 -26
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
- janito/agent/tools/get_lines.py +21 -12
- janito/agent/tools/move_file.py +13 -12
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +150 -0
- janito/agent/tools/python_file_runner.py +148 -0
- janito/agent/tools/python_stdin_runner.py +154 -0
- janito/agent/tools/remove_directory.py +4 -2
- janito/agent/tools/remove_file.py +15 -13
- janito/agent/tools/replace_file.py +72 -0
- janito/agent/tools/replace_text_in_file.py +7 -5
- janito/agent/tools/run_bash_command.py +29 -72
- janito/agent/tools/run_powershell_command.py +142 -102
- janito/agent/tools/search_text.py +177 -131
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +94 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +24 -0
- janito/agent/tools_utils/formatting.py +49 -0
- janito/agent/tools_utils/gitignore_utils.py +69 -0
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +57 -14
- janito/cli/cli_main.py +270 -0
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +166 -99
- janito/cli/one_shot.py +80 -0
- janito/cli/termweb_starter.py +2 -2
- janito/i18n/__init__.py +1 -1
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +41 -25
- janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +74 -0
- janito/shell/commands/edit.py +24 -0
- janito/shell/commands/history_view.py +18 -0
- janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +42 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
- janito/shell/commands/session.py +35 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
- janito/shell/commands/tools.py +26 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +28 -0
- janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
- janito/shell/commands.py +40 -0
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +257 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/shell/session/manager.py +101 -0
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +142 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
- janito-1.10.0.dist-info/RECORD +158 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/gitignore_utils.py +0 -46
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/formatting.py +0 -20
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/run_python_command.py +0 -163
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli/runner/cli_main.py +0 -180
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito/cli_chat_shell/commands/sum.py +0 -49
- janito/cli_chat_shell/commands/utility.py +0 -32
- janito/cli_chat_shell/session_manager.py +0 -72
- janito-1.8.1.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
@@ -8,231 +8,32 @@
|
|
8
8
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
|
9
9
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.css">
|
10
10
|
<link rel="stylesheet" href="/static/termweb.css">
|
11
|
-
<
|
12
|
-
|
13
|
-
height: 100%;
|
14
|
-
margin: 0;
|
15
|
-
padding: 0;
|
16
|
-
}
|
17
|
-
body {
|
18
|
-
display: flex;
|
19
|
-
flex-direction: column;
|
20
|
-
min-height: 100vh;
|
21
|
-
background: #181a1b;
|
22
|
-
color: #eee;
|
23
|
-
transition: background 0.2s, color 0.2s;
|
24
|
-
}
|
25
|
-
body.light-theme {
|
26
|
-
background: #f5f5f5;
|
27
|
-
color: #222;
|
28
|
-
}
|
29
|
-
.main {
|
30
|
-
flex: 1 1 auto;
|
31
|
-
display: flex;
|
32
|
-
flex-direction: column;
|
33
|
-
justify-content: stretch;
|
34
|
-
align-items: stretch;
|
35
|
-
min-height: 0;
|
36
|
-
}
|
37
|
-
.editor-pane {
|
38
|
-
flex: 1 1 auto;
|
39
|
-
display: flex;
|
40
|
-
flex-direction: column;
|
41
|
-
height: 100%;
|
42
|
-
min-height: 0;
|
43
|
-
}
|
44
|
-
.CodeMirror {
|
45
|
-
flex: 1 1 auto;
|
46
|
-
height: 100% !important;
|
47
|
-
min-height: 0;
|
48
|
-
font-size: 1.1em;
|
49
|
-
background: #282a36;
|
50
|
-
color: #f8f8f2;
|
51
|
-
border-radius: 0;
|
52
|
-
border: none;
|
53
|
-
transition: background 0.2s, color 0.2s;
|
54
|
-
}
|
55
|
-
body.light-theme .CodeMirror {
|
56
|
-
background: #fff;
|
57
|
-
color: #222;
|
58
|
-
}
|
59
|
-
.footer {
|
60
|
-
flex-shrink: 0;
|
61
|
-
background: #222;
|
62
|
-
color: #eee;
|
63
|
-
padding: 12px 0 8px 0;
|
64
|
-
text-align: center;
|
65
|
-
width: 100%;
|
66
|
-
position: fixed;
|
67
|
-
left: 0;
|
68
|
-
bottom: 0;
|
69
|
-
z-index: 100;
|
70
|
-
transition: background 0.2s, color 0.2s;
|
71
|
-
}
|
72
|
-
body.light-theme .footer {
|
73
|
-
background: #eaeaea;
|
74
|
-
color: #222;
|
75
|
-
}
|
76
|
-
.footer ul {
|
77
|
-
list-style: none;
|
78
|
-
padding: 0;
|
79
|
-
margin: 8px 0 0 0;
|
80
|
-
display: flex;
|
81
|
-
justify-content: center;
|
82
|
-
gap: 2em;
|
83
|
-
}
|
84
|
-
.footer li {
|
85
|
-
display: inline;
|
86
|
-
}
|
87
|
-
.header {
|
88
|
-
background: #222;
|
89
|
-
color: #fff;
|
90
|
-
padding: 10px 0;
|
91
|
-
text-align: center;
|
92
|
-
font-size: 1.3em;
|
93
|
-
font-weight: bold;
|
94
|
-
position: relative;
|
95
|
-
transition: background 0.2s, color 0.2s;
|
96
|
-
}
|
97
|
-
body.light-theme .header {
|
98
|
-
background: #eaeaea;
|
99
|
-
color: #222;
|
100
|
-
}
|
101
|
-
.theme-switcher {
|
102
|
-
position: absolute;
|
103
|
-
right: 20px;
|
104
|
-
top: 10px;
|
105
|
-
background: #444;
|
106
|
-
color: #fff;
|
107
|
-
border: none;
|
108
|
-
border-radius: 4px;
|
109
|
-
padding: 6px 14px;
|
110
|
-
cursor: pointer;
|
111
|
-
font-size: 1em;
|
112
|
-
transition: background 0.2s, color 0.2s;
|
113
|
-
}
|
114
|
-
body.light-theme .theme-switcher {
|
115
|
-
background: #ddd;
|
116
|
-
color: #222;
|
117
|
-
}
|
118
|
-
</style>
|
11
|
+
<link rel="stylesheet" href="/static/editor.css">
|
12
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.css">
|
119
13
|
</head>
|
120
14
|
<body>
|
121
|
-
<div class="header">
|
122
|
-
TermWeb Editor
|
123
|
-
<
|
124
|
-
|
15
|
+
<div class="header compact-header">
|
16
|
+
<div class="header-title">TermWeb Editor</div>
|
17
|
+
<div class="header-buttons no-shrink">
|
18
|
+
<button class="save-btn" id="save-btn" title="Save (Ctrl+S)">Save</button>
|
19
|
+
<button class="theme-switcher" id="theme-switcher" title="Switch theme">
|
20
|
+
<span id="theme-icon" aria-label="Switch theme">🌙</span>
|
21
|
+
</button>
|
22
|
+
</div>
|
125
23
|
</div>
|
24
|
+
<div id="toast" class="toast"></div>
|
126
25
|
<div class="main">
|
127
26
|
<div class="editor-pane">
|
128
27
|
<textarea id="code" name="code"></textarea>
|
129
28
|
</div>
|
130
29
|
</div>
|
131
|
-
<div class="footer">
|
132
|
-
<div class="subtitle">
|
133
|
-
Desenvolvido por <a href="https://janito.dev" target="_blank">janito.dev</a> — agente de programação com IA
|
134
|
-
</div>
|
135
|
-
<ul>
|
136
|
-
<li>🌐 <a href="https://janito.dev" target="_blank">janito.dev</a></li>
|
137
|
-
<li>📚 <a href="https://docs.janito.dev" target="_blank">Documentation</a></li>
|
138
|
-
<li>💻 <a href="https://github.com/joaompinto/janito" target="_blank">GitHub</a></li>
|
139
|
-
</ul>
|
140
|
-
<span style="font-size:0.9em;opacity:0.7;">_generated by janito.dev_</span>
|
141
|
-
</div>
|
142
30
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
|
143
31
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/python/python.min.js"></script>
|
144
32
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.js"></script>
|
145
|
-
<script>
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
}
|
151
|
-
const filePath = getQueryParam('path');
|
152
|
-
let initialContent = `# Bem-vindo ao TermWeb!\n# Este é um editor CodeMirror ao vivo.\n\nprint("Olá, janito.dev!")`;
|
153
|
-
if (filePath) {
|
154
|
-
fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
|
155
|
-
.then(resp => resp.json())
|
156
|
-
.then(data => {
|
157
|
-
if (data.type === 'file') {
|
158
|
-
initialContent = data.content;
|
159
|
-
if (window.editorInstance) {
|
160
|
-
window.editorInstance.setValue(initialContent);
|
161
|
-
}
|
162
|
-
} else if (data.error) {
|
163
|
-
initialContent = '# Errorr: ' + data.error;
|
164
|
-
if (window.editorInstance) {
|
165
|
-
window.editorInstance.setValue(initialContent);
|
166
|
-
}
|
167
|
-
}
|
168
|
-
})
|
169
|
-
.catch(err => {
|
170
|
-
initialContent = '# Error ao carregar arquivo: ' + err;
|
171
|
-
if (window.editorInstance) {
|
172
|
-
window.editorInstance.setValue(initialContent);
|
173
|
-
}
|
174
|
-
});
|
175
|
-
}
|
176
|
-
document.addEventListener('DOMContentLoaded', function() {
|
177
|
-
var editorInstance = CodeMirror.fromTextArea(document.getElementById('code'), {
|
178
|
-
lineNumbers: true,
|
179
|
-
mode: 'python',
|
180
|
-
theme: 'dracula',
|
181
|
-
indentUnit: 4,
|
182
|
-
tabSize: 4,
|
183
|
-
});
|
184
|
-
window.editorInstance = editorInstance;
|
185
|
-
editorInstance.setSize('100%', 'calc(100vh - 60px - 70px)'); // header/footer height
|
186
|
-
editorInstance.setValue(initialContent);
|
187
|
-
|
188
|
-
// Theme switcher logic
|
189
|
-
var themeSwitcher = document.getElementById('theme-switcher');
|
190
|
-
var isDark = true;
|
191
|
-
themeSwitcher.addEventListener('click', function() {
|
192
|
-
isDark = !isDark;
|
193
|
-
if (isDark) {
|
194
|
-
document.body.classList.remove('light-theme');
|
195
|
-
editorInstance.setOption('theme', 'dracula');
|
196
|
-
themeSwitcher.textContent = 'Alternate to light theme';
|
197
|
-
} else {
|
198
|
-
document.body.classList.add('light-theme');
|
199
|
-
editorInstance.setOption('theme', 'default');
|
200
|
-
themeSwitcher.textContent = 'Alternate to dark theme';
|
201
|
-
}
|
202
|
-
});
|
203
|
-
// Set initial state
|
204
|
-
document.body.classList.remove('light-theme');
|
205
|
-
editorInstance.setOption('theme', 'dracula');
|
206
|
-
themeSwitcher.textContent = 'Alternate to light theme';
|
207
|
-
// Botão de Gravar
|
208
|
-
var saveBtn = document.getElementById('save-btn');
|
209
|
-
saveBtn.addEventListener('click', function() {
|
210
|
-
if (!filePath) {
|
211
|
-
alert('Nenhum arquivo aberto para gravar.');
|
212
|
-
return;
|
213
|
-
}
|
214
|
-
const content = editorInstance.getValue();
|
215
|
-
fetch(`/api/explorer/${encodeURIComponent(filePath)}`, {
|
216
|
-
method: 'POST',
|
217
|
-
headers: {
|
218
|
-
'Content-Type': 'application/json',
|
219
|
-
},
|
220
|
-
body: JSON.stringify({ content }),
|
221
|
-
})
|
222
|
-
.then(resp => resp.json())
|
223
|
-
.then(data => {
|
224
|
-
if (data.success) {
|
225
|
-
saveBtn.textContent = 'Gravado!';
|
226
|
-
setTimeout(() => saveBtn.textContent = 'Gravar', 1200);
|
227
|
-
} else {
|
228
|
-
alert('Errorr saving: ' + (data.error || 'desconhecido'));
|
229
|
-
}
|
230
|
-
})
|
231
|
-
.catch(err => {
|
232
|
-
alert('Errorr saving: ' + err);
|
233
|
-
});
|
234
|
-
});
|
235
|
-
});
|
236
|
-
</script>
|
33
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/selection/active-line.min.js"></script>
|
34
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/search.min.js"></script>
|
35
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/searchcursor.min.js"></script>
|
36
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.js"></script>
|
37
|
+
<script src="/static/editor.js"></script>
|
237
38
|
</body>
|
238
39
|
</html>
|
@@ -0,0 +1,209 @@
|
|
1
|
+
// --- Load file content via AJAX if ?path=... is present ---
|
2
|
+
function getQueryParam(name) {
|
3
|
+
const url = new URL(window.location.href);
|
4
|
+
return url.searchParams.get(name);
|
5
|
+
}
|
6
|
+
const filePath = getQueryParam('path');
|
7
|
+
let initialContent = `# Bem-vindo ao TermWeb!\n# Este é um editor CodeMirror ao vivo.\n\nprint("Olá, janito.dev!")`;
|
8
|
+
if (filePath) {
|
9
|
+
fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
|
10
|
+
.then(resp => resp.json())
|
11
|
+
.then(data => {
|
12
|
+
if (data.type === 'file') {
|
13
|
+
initialContent = data.content;
|
14
|
+
if (window.editorInstance) {
|
15
|
+
window.editorInstance.setValue(initialContent);
|
16
|
+
}
|
17
|
+
} else if (data.error) {
|
18
|
+
initialContent = '# Error: ' + data.error;
|
19
|
+
if (window.editorInstance) {
|
20
|
+
window.editorInstance.setValue(initialContent);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
})
|
24
|
+
.catch(err => {
|
25
|
+
initialContent = '# Error ao carregar arquivo: ' + err;
|
26
|
+
if (window.editorInstance) {
|
27
|
+
window.editorInstance.setValue(initialContent);
|
28
|
+
}
|
29
|
+
});
|
30
|
+
}
|
31
|
+
document.addEventListener('DOMContentLoaded', function() {
|
32
|
+
var editorInstance = CodeMirror.fromTextArea(document.getElementById('code'), {
|
33
|
+
lineNumbers: true,
|
34
|
+
mode: 'python',
|
35
|
+
theme: 'dracula',
|
36
|
+
indentUnit: 4,
|
37
|
+
tabSize: 4,
|
38
|
+
styleActiveLine: true,
|
39
|
+
});
|
40
|
+
window.editorInstance = editorInstance;
|
41
|
+
// Add Ctrl-F handler for find
|
42
|
+
editorInstance.addKeyMap({
|
43
|
+
'Ctrl-F': function(cm) {
|
44
|
+
cm.execCommand('find');
|
45
|
+
},
|
46
|
+
'Cmd-F': function(cm) {
|
47
|
+
cm.execCommand('find');
|
48
|
+
},
|
49
|
+
'Ctrl-S': function(cm) {
|
50
|
+
document.getElementById('save-btn').click();
|
51
|
+
// Prevent default browser save dialog
|
52
|
+
return false;
|
53
|
+
},
|
54
|
+
'Cmd-S': function(cm) {
|
55
|
+
document.getElementById('save-btn').click();
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
});
|
59
|
+
// --- Custom floating find navigation panel ---
|
60
|
+
function createFindPanel() {
|
61
|
+
let panel = document.getElementById('find-nav-panel');
|
62
|
+
if (!panel) {
|
63
|
+
panel = document.createElement('div');
|
64
|
+
panel.id = 'find-nav-panel';
|
65
|
+
panel.style.position = 'absolute';
|
66
|
+
panel.style.top = '70px';
|
67
|
+
panel.style.right = '30px';
|
68
|
+
panel.style.zIndex = 200;
|
69
|
+
panel.style.background = 'rgba(40,42,54,0.98)';
|
70
|
+
panel.style.color = '#fff';
|
71
|
+
panel.style.borderRadius = '6px';
|
72
|
+
panel.style.boxShadow = '0 2px 8px #0008';
|
73
|
+
panel.style.padding = '4px 10px 4px 8px';
|
74
|
+
panel.style.display = 'flex';
|
75
|
+
panel.style.alignItems = 'center';
|
76
|
+
panel.style.gap = '8px';
|
77
|
+
panel.style.fontSize = '1em';
|
78
|
+
panel.style.userSelect = 'none';
|
79
|
+
panel.innerHTML = `
|
80
|
+
<button id="find-prev-btn" title="Previous match" style="background:none;border:none;color:#fff;font-size:1.2em;cursor:pointer;">⏮️</button>
|
81
|
+
<span id="find-match-info">1/1</span>
|
82
|
+
<button id="find-next-btn" title="Next match" style="background:none;border:none;color:#fff;font-size:1.2em;cursor:pointer;">⏭️</button>
|
83
|
+
`;
|
84
|
+
document.body.appendChild(panel);
|
85
|
+
}
|
86
|
+
return panel;
|
87
|
+
}
|
88
|
+
function removeFindPanel() {
|
89
|
+
let panel = document.getElementById('find-nav-panel');
|
90
|
+
if (panel) panel.remove();
|
91
|
+
}
|
92
|
+
// Hook into CodeMirror search dialog
|
93
|
+
let lastSearchState = null;
|
94
|
+
function updateFindPanel(cm) {
|
95
|
+
let state = cm.state.search;
|
96
|
+
if (!state || !state.query) {
|
97
|
+
removeFindPanel();
|
98
|
+
return;
|
99
|
+
}
|
100
|
+
let matches = 0, current = 0;
|
101
|
+
if (state.overlay && state.overlay.matches) {
|
102
|
+
matches = state.overlay.matches.length;
|
103
|
+
current = state.overlay.matches.findIndex(m => m.from && cm.getCursor().line === m.from.line && cm.getCursor().ch >= m.from.ch && cm.getCursor().ch <= m.to.ch) + 1;
|
104
|
+
}
|
105
|
+
// fallback: count marked text
|
106
|
+
if (!matches) {
|
107
|
+
let marks = cm.getAllMarks();
|
108
|
+
let found = marks.filter(m => m.className && m.className.includes('cm-searching'));
|
109
|
+
matches = found.length;
|
110
|
+
let cur = cm.getCursor();
|
111
|
+
current = found.findIndex(m => {
|
112
|
+
let pos = m.find();
|
113
|
+
return pos && pos.from.line === cur.line && cur.ch >= pos.from.ch && cur.ch <= pos.to.ch;
|
114
|
+
}) + 1;
|
115
|
+
}
|
116
|
+
if (!matches) matches = 1;
|
117
|
+
if (!current) current = 1;
|
118
|
+
createFindPanel();
|
119
|
+
document.getElementById('find-match-info').textContent = `${current}/${matches}`;
|
120
|
+
}
|
121
|
+
editorInstance.on('cursorActivity', function(cm) {
|
122
|
+
updateFindPanel(cm);
|
123
|
+
});
|
124
|
+
editorInstance.on('search', function(cm) {
|
125
|
+
updateFindPanel(cm);
|
126
|
+
});
|
127
|
+
// Listen for dialog open/close
|
128
|
+
let observer = new MutationObserver(function() {
|
129
|
+
let dialogs = document.querySelectorAll('.CodeMirror-dialog');
|
130
|
+
if (dialogs.length) {
|
131
|
+
setTimeout(() => updateFindPanel(editorInstance), 100);
|
132
|
+
} else {
|
133
|
+
removeFindPanel();
|
134
|
+
}
|
135
|
+
});
|
136
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
137
|
+
// Button handlers
|
138
|
+
document.body.addEventListener('click', function(e) {
|
139
|
+
if (e.target && e.target.id === 'find-next-btn') {
|
140
|
+
editorInstance.execCommand('findNext');
|
141
|
+
} else if (e.target && e.target.id === 'find-prev-btn') {
|
142
|
+
editorInstance.execCommand('findPrev');
|
143
|
+
}
|
144
|
+
});
|
145
|
+
// --- END custom find panel ---
|
146
|
+
// Dynamically calculate available height
|
147
|
+
function resizeEditor() {
|
148
|
+
var header = document.querySelector('.header');
|
149
|
+
var headerHeight = header ? header.offsetHeight : 0;
|
150
|
+
var availableHeight = window.innerHeight - headerHeight;
|
151
|
+
editorInstance.setSize('100%', availableHeight + 'px');
|
152
|
+
}
|
153
|
+
window.addEventListener('resize', resizeEditor);
|
154
|
+
setTimeout(resizeEditor, 0);
|
155
|
+
editorInstance.setValue(initialContent);
|
156
|
+
|
157
|
+
// Theme switcher logic
|
158
|
+
var themeSwitcher = document.getElementById('theme-switcher');
|
159
|
+
var themeIcon = document.getElementById('theme-icon');
|
160
|
+
var isDark = true;
|
161
|
+
function updateThemeIcon() {
|
162
|
+
themeIcon.textContent = isDark ? '🌙' : '☀️';
|
163
|
+
}
|
164
|
+
themeSwitcher.addEventListener('click', function() {
|
165
|
+
isDark = !isDark;
|
166
|
+
if (isDark) {
|
167
|
+
document.body.classList.remove('light-theme');
|
168
|
+
editorInstance.setOption('theme', 'dracula');
|
169
|
+
} else {
|
170
|
+
document.body.classList.add('light-theme');
|
171
|
+
editorInstance.setOption('theme', 'default');
|
172
|
+
}
|
173
|
+
updateThemeIcon();
|
174
|
+
});
|
175
|
+
// Set initial state
|
176
|
+
document.body.classList.remove('light-theme');
|
177
|
+
editorInstance.setOption('theme', 'dracula');
|
178
|
+
updateThemeIcon();
|
179
|
+
// Botão de Gravar
|
180
|
+
var saveBtn = document.getElementById('save-btn');
|
181
|
+
saveBtn.addEventListener('click', function() {
|
182
|
+
if (!filePath) {
|
183
|
+
alert('Nenhum arquivo aberto para gravar.');
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
const content = editorInstance.getValue();
|
187
|
+
fetch(`/api/explorer/${encodeURIComponent(filePath)}`, {
|
188
|
+
method: 'POST',
|
189
|
+
headers: {
|
190
|
+
'Content-Type': 'application/json',
|
191
|
+
},
|
192
|
+
body: JSON.stringify({ content }),
|
193
|
+
})
|
194
|
+
.then(resp => resp.json())
|
195
|
+
.then(data => {
|
196
|
+
if (data.success) {
|
197
|
+
// Show popup
|
198
|
+
var popup = document.getElementById('save-popup');
|
199
|
+
popup.style.display = 'block';
|
200
|
+
setTimeout(() => { popup.style.display = 'none'; }, 1500);
|
201
|
+
} else {
|
202
|
+
alert('Error saving: ' + (data.error || 'desconhecido'));
|
203
|
+
}
|
204
|
+
})
|
205
|
+
.catch(err => {
|
206
|
+
alert('Error saving: ' + err);
|
207
|
+
});
|
208
|
+
});
|
209
|
+
});
|
@@ -0,0 +1,227 @@
|
|
1
|
+
// --- Load file content via AJAX if ?path=... is present ---
|
2
|
+
function getQueryParam(name) {
|
3
|
+
const url = new URL(window.location.href);
|
4
|
+
return url.searchParams.get(name);
|
5
|
+
}
|
6
|
+
const filePath = getQueryParam('path');
|
7
|
+
let initialContent = `# Bem-vindo ao TermWeb!\n# Este é um editor CodeMirror ao vivo.\n\nprint("Olá, janito.dev!")`;
|
8
|
+
if (filePath) {
|
9
|
+
fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
|
10
|
+
.then(resp => resp.json())
|
11
|
+
.then(data => {
|
12
|
+
if (data.type === 'file') {
|
13
|
+
initialContent = data.content;
|
14
|
+
if (window.editorInstance) {
|
15
|
+
window.editorInstance.setValue(initialContent);
|
16
|
+
}
|
17
|
+
} else if (data.error) {
|
18
|
+
initialContent = '# Error: ' + data.error;
|
19
|
+
if (window.editorInstance) {
|
20
|
+
window.editorInstance.setValue(initialContent);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
})
|
24
|
+
.catch(err => {
|
25
|
+
initialContent = '# Error ao carregar arquivo: ' + err;
|
26
|
+
if (window.editorInstance) {
|
27
|
+
window.editorInstance.setValue(initialContent);
|
28
|
+
}
|
29
|
+
});
|
30
|
+
}
|
31
|
+
document.addEventListener('DOMContentLoaded', function() {
|
32
|
+
var editorInstance = CodeMirror.fromTextArea(document.getElementById('code'), {
|
33
|
+
lineNumbers: true,
|
34
|
+
mode: 'python',
|
35
|
+
theme: 'dracula',
|
36
|
+
indentUnit: 4,
|
37
|
+
tabSize: 4,
|
38
|
+
styleActiveLine: true,
|
39
|
+
});
|
40
|
+
window.editorInstance = editorInstance;
|
41
|
+
// Add Ctrl-F handler for find
|
42
|
+
editorInstance.addKeyMap({
|
43
|
+
'Ctrl-F': function(cm) {
|
44
|
+
cm.execCommand('find');
|
45
|
+
},
|
46
|
+
'Cmd-F': function(cm) {
|
47
|
+
cm.execCommand('find');
|
48
|
+
}
|
49
|
+
});
|
50
|
+
// Add Ctrl+S/Cmd+S handler for save
|
51
|
+
editorInstance.addKeyMap({
|
52
|
+
'Ctrl-S': function(cm) {
|
53
|
+
document.getElementById('save-btn').click();
|
54
|
+
},
|
55
|
+
'Cmd-S': function(cm) {
|
56
|
+
document.getElementById('save-btn').click();
|
57
|
+
}
|
58
|
+
});
|
59
|
+
// --- Custom floating find navigation panel ---
|
60
|
+
function createFindPanel() {
|
61
|
+
let panel = document.getElementById('find-nav-panel');
|
62
|
+
if (!panel) {
|
63
|
+
panel = document.createElement('div');
|
64
|
+
panel.id = 'find-nav-panel';
|
65
|
+
panel.style.position = 'absolute';
|
66
|
+
panel.style.top = '70px';
|
67
|
+
panel.style.right = '30px';
|
68
|
+
panel.style.zIndex = 200;
|
69
|
+
panel.style.background = 'rgba(40,42,54,0.98)';
|
70
|
+
panel.style.color = '#fff';
|
71
|
+
panel.style.borderRadius = '6px';
|
72
|
+
panel.style.boxShadow = '0 2px 8px #0008';
|
73
|
+
panel.style.padding = '4px 10px 4px 8px';
|
74
|
+
panel.style.display = 'flex';
|
75
|
+
panel.style.alignItems = 'center';
|
76
|
+
panel.style.gap = '8px';
|
77
|
+
panel.style.fontSize = '1em';
|
78
|
+
panel.style.userSelect = 'none';
|
79
|
+
panel.innerHTML = `
|
80
|
+
<button id="find-prev-btn" title="Previous match" style="background:none;border:none;color:#fff;font-size:1.2em;cursor:pointer;">⏮️</button>
|
81
|
+
<span id="find-match-info">1/1</span>
|
82
|
+
<button id="find-next-btn" title="Next match" style="background:none;border:none;color:#fff;font-size:1.2em;cursor:pointer;">⏭️</button>
|
83
|
+
`;
|
84
|
+
document.body.appendChild(panel);
|
85
|
+
}
|
86
|
+
return panel;
|
87
|
+
}
|
88
|
+
function removeFindPanel() {
|
89
|
+
let panel = document.getElementById('find-nav-panel');
|
90
|
+
if (panel) panel.remove();
|
91
|
+
}
|
92
|
+
// Hook into CodeMirror search dialog
|
93
|
+
let lastSearchState = null;
|
94
|
+
function updateFindPanel(cm) {
|
95
|
+
let state = cm.state.search;
|
96
|
+
if (!state || !state.query) {
|
97
|
+
removeFindPanel();
|
98
|
+
return;
|
99
|
+
}
|
100
|
+
let matches = 0, current = 0;
|
101
|
+
if (state.overlay && state.overlay.matches) {
|
102
|
+
matches = state.overlay.matches.length;
|
103
|
+
current = state.overlay.matches.findIndex(m => m.from && cm.getCursor().line === m.from.line && cm.getCursor().ch >= m.from.ch && cm.getCursor().ch <= m.to.ch) + 1;
|
104
|
+
}
|
105
|
+
// fallback: count marked text
|
106
|
+
if (!matches) {
|
107
|
+
let marks = cm.getAllMarks();
|
108
|
+
let found = marks.filter(m => m.className && m.className.includes('cm-searching'));
|
109
|
+
matches = found.length;
|
110
|
+
let cur = cm.getCursor();
|
111
|
+
current = found.findIndex(m => {
|
112
|
+
let pos = m.find();
|
113
|
+
return pos && pos.from.line === cur.line && cur.ch >= pos.from.ch && cur.ch <= pos.to.ch;
|
114
|
+
}) + 1;
|
115
|
+
}
|
116
|
+
if (!matches) matches = 1;
|
117
|
+
if (!current) current = 1;
|
118
|
+
createFindPanel();
|
119
|
+
document.getElementById('find-match-info').textContent = `${current}/${matches}`;
|
120
|
+
}
|
121
|
+
editorInstance.on('cursorActivity', function(cm) {
|
122
|
+
updateFindPanel(cm);
|
123
|
+
});
|
124
|
+
editorInstance.on('search', function(cm) {
|
125
|
+
updateFindPanel(cm);
|
126
|
+
});
|
127
|
+
// Listen for dialog open/close
|
128
|
+
let observer = new MutationObserver(function() {
|
129
|
+
let dialogs = document.querySelectorAll('.CodeMirror-dialog');
|
130
|
+
if (dialogs.length) {
|
131
|
+
setTimeout(() => updateFindPanel(editorInstance), 100);
|
132
|
+
} else {
|
133
|
+
removeFindPanel();
|
134
|
+
}
|
135
|
+
});
|
136
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
137
|
+
// Button handlers
|
138
|
+
document.body.addEventListener('click', function(e) {
|
139
|
+
if (e.target && e.target.id === 'find-next-btn') {
|
140
|
+
editorInstance.execCommand('findNext');
|
141
|
+
} else if (e.target && e.target.id === 'find-prev-btn') {
|
142
|
+
editorInstance.execCommand('findPrev');
|
143
|
+
}
|
144
|
+
});
|
145
|
+
// --- END custom find panel ---
|
146
|
+
// Dynamically calculate available height
|
147
|
+
function resizeEditor() {
|
148
|
+
var header = document.querySelector('.header');
|
149
|
+
var headerHeight = header ? header.offsetHeight : 0;
|
150
|
+
var availableHeight = window.innerHeight - headerHeight;
|
151
|
+
editorInstance.setSize('100%', availableHeight + 'px');
|
152
|
+
}
|
153
|
+
window.addEventListener('resize', resizeEditor);
|
154
|
+
setTimeout(resizeEditor, 0);
|
155
|
+
editorInstance.setValue(initialContent);
|
156
|
+
|
157
|
+
// Theme switcher logic
|
158
|
+
var themeSwitcher = document.getElementById('theme-switcher');
|
159
|
+
var themeIcon = document.getElementById('theme-icon');
|
160
|
+
var isDark = true;
|
161
|
+
function updateThemeIcon() {
|
162
|
+
themeIcon.textContent = isDark ? '🌙' : '☀️';
|
163
|
+
}
|
164
|
+
themeSwitcher.addEventListener('click', function() {
|
165
|
+
isDark = !isDark;
|
166
|
+
if (isDark) {
|
167
|
+
document.body.classList.remove('light-theme');
|
168
|
+
editorInstance.setOption('theme', 'dracula');
|
169
|
+
} else {
|
170
|
+
document.body.classList.add('light-theme');
|
171
|
+
editorInstance.setOption('theme', 'default');
|
172
|
+
}
|
173
|
+
updateThemeIcon();
|
174
|
+
});
|
175
|
+
// Set initial state
|
176
|
+
document.body.classList.remove('light-theme');
|
177
|
+
editorInstance.setOption('theme', 'dracula');
|
178
|
+
updateThemeIcon();
|
179
|
+
|
180
|
+
// Toast notification logic
|
181
|
+
function showToast(message) {
|
182
|
+
var toast = document.getElementById('toast');
|
183
|
+
toast.textContent = message;
|
184
|
+
toast.className = 'toast show';
|
185
|
+
setTimeout(function() {
|
186
|
+
toast.className = 'toast';
|
187
|
+
}, 1800);
|
188
|
+
}
|
189
|
+
|
190
|
+
// Save Button
|
191
|
+
var saveBtn = document.getElementById('save-btn');
|
192
|
+
saveBtn.addEventListener('click', function() {
|
193
|
+
if (!filePath) {
|
194
|
+
alert('Nenhum arquivo aberto para gravar.');
|
195
|
+
return;
|
196
|
+
}
|
197
|
+
const content = editorInstance.getValue();
|
198
|
+
fetch(`/api/explorer/${encodeURIComponent(filePath)}`, {
|
199
|
+
method: 'POST',
|
200
|
+
headers: {
|
201
|
+
'Content-Type': 'application/json',
|
202
|
+
},
|
203
|
+
body: JSON.stringify({ content }),
|
204
|
+
})
|
205
|
+
.then(resp => resp.json())
|
206
|
+
.then(data => {
|
207
|
+
if (data.success) {
|
208
|
+
showToast('Saved!');
|
209
|
+
saveBtn.textContent = 'Saved!';
|
210
|
+
setTimeout(() => saveBtn.textContent = 'Save', 1200);
|
211
|
+
} else {
|
212
|
+
alert('Error saving: ' + (data.error || 'desconhecido'));
|
213
|
+
}
|
214
|
+
})
|
215
|
+
.catch(err => {
|
216
|
+
alert('Error saving: ' + err);
|
217
|
+
});
|
218
|
+
});
|
219
|
+
|
220
|
+
// Prevent browser default save dialog on Ctrl+S/Cmd+S
|
221
|
+
window.addEventListener('keydown', function(e) {
|
222
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
|
223
|
+
e.preventDefault();
|
224
|
+
document.getElementById('save-btn').click();
|
225
|
+
}
|
226
|
+
});
|
227
|
+
});
|