archsight 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/chart/archsight/Chart.yaml +6 -0
- data/chart/archsight/README.md +160 -0
- data/chart/archsight/templates/NOTES.txt +22 -0
- data/chart/archsight/templates/_helpers.tpl +62 -0
- data/chart/archsight/templates/deployment.yaml +114 -0
- data/chart/archsight/templates/ingress.yaml +56 -0
- data/chart/archsight/templates/resources-configmap.yaml +10 -0
- data/chart/archsight/templates/resources-pvc.yaml +23 -0
- data/chart/archsight/templates/service.yaml +15 -0
- data/chart/archsight/templates/serviceaccount.yaml +12 -0
- data/chart/archsight/values.yaml +162 -0
- data/lib/archsight/analysis/executor.rb +0 -10
- data/lib/archsight/annotations/annotation.rb +85 -36
- data/lib/archsight/annotations/architecture_annotations.rb +1 -34
- data/lib/archsight/annotations/computed.rb +1 -1
- data/lib/archsight/annotations/generated_annotations.rb +9 -2
- data/lib/archsight/annotations/git_annotations.rb +8 -4
- data/lib/archsight/annotations/interface_annotations.rb +35 -0
- data/lib/archsight/cli.rb +3 -1
- data/lib/archsight/editor/content_hasher.rb +37 -0
- data/lib/archsight/editor/file_writer.rb +79 -0
- data/lib/archsight/editor.rb +237 -0
- data/lib/archsight/import/handlers/github.rb +14 -6
- data/lib/archsight/import/handlers/gitlab.rb +14 -6
- data/lib/archsight/import/handlers/repository.rb +3 -1
- data/lib/archsight/import/team_matcher.rb +111 -61
- data/lib/archsight/mcp/execute_analysis_tool.rb +100 -0
- data/lib/archsight/mcp.rb +1 -0
- data/lib/archsight/resources/analysis.rb +1 -17
- data/lib/archsight/resources/application_interface.rb +1 -5
- data/lib/archsight/resources/base.rb +14 -14
- data/lib/archsight/resources/business_actor.rb +1 -1
- data/lib/archsight/resources/technology_interface.rb +1 -1
- data/lib/archsight/resources/technology_service.rb +5 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/application.rb +8 -0
- data/lib/archsight/web/doc/import.md +10 -2
- data/lib/archsight/web/editor/form_builder.rb +100 -0
- data/lib/archsight/web/editor/routes.rb +293 -0
- data/lib/archsight/web/public/css/editor.css +863 -0
- data/lib/archsight/web/public/css/instance.css +6 -0
- data/lib/archsight/web/public/js/editor.js +421 -0
- data/lib/archsight/web/public/js/lexical-editor.js +308 -0
- data/lib/archsight/web/views/partials/editor/_field.haml +80 -0
- data/lib/archsight/web/views/partials/editor/_form.haml +131 -0
- data/lib/archsight/web/views/partials/editor/_relations.haml +39 -0
- data/lib/archsight/web/views/partials/editor/_yaml_output.haml +33 -0
- data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +4 -11
- data/lib/archsight/web/views/partials/instance/_detail.haml +4 -0
- data/lib/archsight/web/views/partials/layout/_content.haml +8 -2
- data/lib/archsight/web/views/partials/layout/_head.haml +2 -0
- metadata +26 -1
|
@@ -383,10 +383,16 @@
|
|
|
383
383
|
margin: 0;
|
|
384
384
|
white-space: pre;
|
|
385
385
|
overflow-x: auto;
|
|
386
|
+
min-height: 300px;
|
|
387
|
+
max-height: 600px;
|
|
388
|
+
overflow-y: auto;
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
.analysis-script pre.code code {
|
|
389
392
|
white-space: pre;
|
|
393
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
394
|
+
font-size: 0.9em;
|
|
395
|
+
line-height: 1.5;
|
|
390
396
|
}
|
|
391
397
|
|
|
392
398
|
.analysis-execution header {
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
// Editor JavaScript - Form handling, relation management, and markdown editor
|
|
2
|
+
|
|
3
|
+
// Import Lexical editor module (only when needed)
|
|
4
|
+
let lexicalModule = null;
|
|
5
|
+
let lexicalInitialized = false;
|
|
6
|
+
let currentTextareaId = null;
|
|
7
|
+
|
|
8
|
+
// Lazy load Lexical module
|
|
9
|
+
async function loadLexicalModule() {
|
|
10
|
+
if (!lexicalModule) {
|
|
11
|
+
lexicalModule = await import('./lexical-editor.js');
|
|
12
|
+
}
|
|
13
|
+
return lexicalModule;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Open the markdown editor overlay
|
|
17
|
+
async function openMarkdownEditor(textareaId) {
|
|
18
|
+
const overlay = document.getElementById('markdown-editor-overlay');
|
|
19
|
+
const textarea = document.getElementById(textareaId);
|
|
20
|
+
|
|
21
|
+
if (!overlay || !textarea) return;
|
|
22
|
+
|
|
23
|
+
currentTextareaId = textareaId;
|
|
24
|
+
|
|
25
|
+
// Load Lexical module if not already loaded
|
|
26
|
+
const { initLexicalEditor, setLexicalMarkdown } = await loadLexicalModule();
|
|
27
|
+
|
|
28
|
+
// Initialize Lexical if not already done
|
|
29
|
+
if (!lexicalInitialized) {
|
|
30
|
+
const editorRoot = document.getElementById('lexical-editor-root');
|
|
31
|
+
if (editorRoot) {
|
|
32
|
+
initLexicalEditor(editorRoot);
|
|
33
|
+
lexicalInitialized = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Load content from textarea
|
|
38
|
+
setLexicalMarkdown(textarea.value);
|
|
39
|
+
|
|
40
|
+
// Show overlay
|
|
41
|
+
overlay.classList.remove('hidden');
|
|
42
|
+
document.body.style.overflow = 'hidden';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Save content from Lexical editor back to textarea
|
|
46
|
+
async function saveMarkdownEditor() {
|
|
47
|
+
const textarea = document.getElementById(currentTextareaId);
|
|
48
|
+
|
|
49
|
+
if (textarea && lexicalModule) {
|
|
50
|
+
const markdown = await lexicalModule.getLexicalMarkdown();
|
|
51
|
+
textarea.value = markdown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
closeMarkdownEditor();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Close the markdown editor overlay
|
|
58
|
+
function closeMarkdownEditor() {
|
|
59
|
+
const overlay = document.getElementById('markdown-editor-overlay');
|
|
60
|
+
if (overlay) {
|
|
61
|
+
overlay.classList.add('hidden');
|
|
62
|
+
}
|
|
63
|
+
document.body.style.overflow = '';
|
|
64
|
+
currentTextareaId = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Format text (bold, italic, etc.)
|
|
68
|
+
function formatLexical(format) {
|
|
69
|
+
if (lexicalModule) {
|
|
70
|
+
lexicalModule.formatText(format);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Format block (headings, lists, quotes)
|
|
75
|
+
function formatLexicalBlock(blockType) {
|
|
76
|
+
if (lexicalModule) {
|
|
77
|
+
lexicalModule.formatBlock(blockType);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Insert a code block
|
|
82
|
+
function insertCodeBlock() {
|
|
83
|
+
if (lexicalModule) {
|
|
84
|
+
lexicalModule.insertCodeBlock('');
|
|
85
|
+
// Show language selector after inserting
|
|
86
|
+
setTimeout(() => updateCodeLanguageSelector(), 50);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Apply language from the selector dropdown
|
|
91
|
+
function applyCodeLanguage(language) {
|
|
92
|
+
if (lexicalModule && language) {
|
|
93
|
+
lexicalModule.setCodeBlockLanguage(language);
|
|
94
|
+
}
|
|
95
|
+
hideCodeLanguageSelector();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Show/hide the code language selector in toolbar
|
|
99
|
+
function updateCodeLanguageSelector() {
|
|
100
|
+
const picker = document.getElementById('code-language-picker');
|
|
101
|
+
if (!picker || !lexicalModule) return;
|
|
102
|
+
|
|
103
|
+
const currentLang = lexicalModule.getCurrentCodeLanguage();
|
|
104
|
+
|
|
105
|
+
if (currentLang !== null) {
|
|
106
|
+
// We're in a code block - show selector and set current value
|
|
107
|
+
picker.classList.remove('hidden');
|
|
108
|
+
picker.value = currentLang || '';
|
|
109
|
+
} else {
|
|
110
|
+
picker.classList.add('hidden');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hideCodeLanguageSelector() {
|
|
115
|
+
const picker = document.getElementById('code-language-picker');
|
|
116
|
+
if (picker) {
|
|
117
|
+
picker.classList.add('hidden');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Insert a link
|
|
122
|
+
function insertLink() {
|
|
123
|
+
if (lexicalModule) {
|
|
124
|
+
lexicalModule.insertLink();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// Expose markdown editor functions to global scope for onclick handlers
|
|
130
|
+
window.openMarkdownEditor = openMarkdownEditor;
|
|
131
|
+
window.saveMarkdownEditor = saveMarkdownEditor;
|
|
132
|
+
window.closeMarkdownEditor = closeMarkdownEditor;
|
|
133
|
+
window.formatLexical = formatLexical;
|
|
134
|
+
window.formatLexicalBlock = formatLexicalBlock;
|
|
135
|
+
window.insertCodeBlock = insertCodeBlock;
|
|
136
|
+
window.applyCodeLanguage = applyCodeLanguage;
|
|
137
|
+
window.insertLink = insertLink;
|
|
138
|
+
|
|
139
|
+
// Add a new relation from the inline form
|
|
140
|
+
function addRelation() {
|
|
141
|
+
const verbKindSelect = document.getElementById('new-relation-verb-kind');
|
|
142
|
+
const instanceSelect = document.getElementById('new-relation-instance');
|
|
143
|
+
|
|
144
|
+
if (!verbKindSelect || !instanceSelect) return;
|
|
145
|
+
|
|
146
|
+
const verbKind = verbKindSelect.value;
|
|
147
|
+
const instance = instanceSelect.value;
|
|
148
|
+
|
|
149
|
+
// Validate all fields are selected
|
|
150
|
+
if (!verbKind || !instance) return;
|
|
151
|
+
|
|
152
|
+
// Parse verb:kind format
|
|
153
|
+
const [verb, kind] = verbKind.split(':', 2);
|
|
154
|
+
if (!verb || !kind) return;
|
|
155
|
+
|
|
156
|
+
// Create relation item with hidden inputs
|
|
157
|
+
const item = document.createElement('div');
|
|
158
|
+
item.className = 'relation-item';
|
|
159
|
+
item.innerHTML = `
|
|
160
|
+
<span class="relation-text">
|
|
161
|
+
<strong>${escapeHtml(verb)}</strong> → ${escapeHtml(kind)}: <em>${escapeHtml(instance)}</em>
|
|
162
|
+
</span>
|
|
163
|
+
<input type="hidden" name="relations[][verb]" value="${escapeHtml(verb)}">
|
|
164
|
+
<input type="hidden" name="relations[][kind]" value="${escapeHtml(kind)}">
|
|
165
|
+
<input type="hidden" name="relations[][name]" value="${escapeHtml(instance)}">
|
|
166
|
+
<button class="btn-remove" type="button" onclick="removeRelation(this)" title="Remove">
|
|
167
|
+
<i class="iconoir-xmark"></i>
|
|
168
|
+
</button>
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
// Add to list
|
|
172
|
+
const relationsList = document.getElementById('relations-list');
|
|
173
|
+
if (relationsList) {
|
|
174
|
+
relationsList.appendChild(item);
|
|
175
|
+
sortRelations();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Clear the form
|
|
179
|
+
clearAddForm();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Remove a relation from the list
|
|
183
|
+
function removeRelation(button) {
|
|
184
|
+
const item = button.closest('.relation-item');
|
|
185
|
+
if (item) {
|
|
186
|
+
item.remove();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Sort relations alphabetically by verb, kind, instance
|
|
191
|
+
function sortRelations() {
|
|
192
|
+
const relationsList = document.getElementById('relations-list');
|
|
193
|
+
if (!relationsList) return;
|
|
194
|
+
|
|
195
|
+
const items = Array.from(relationsList.querySelectorAll('.relation-item'));
|
|
196
|
+
if (items.length === 0) return;
|
|
197
|
+
|
|
198
|
+
items.sort((a, b) => {
|
|
199
|
+
const getValues = (el) => {
|
|
200
|
+
const verb = el.querySelector('input[name="relations[][verb]"]')?.value || '';
|
|
201
|
+
const kind = el.querySelector('input[name="relations[][kind]"]')?.value || '';
|
|
202
|
+
const name = el.querySelector('input[name="relations[][name]"]')?.value || '';
|
|
203
|
+
return [verb, kind, name];
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const [aVerb, aKind, aName] = getValues(a);
|
|
207
|
+
const [bVerb, bKind, bName] = getValues(b);
|
|
208
|
+
|
|
209
|
+
return aVerb.localeCompare(bVerb) ||
|
|
210
|
+
aKind.localeCompare(bKind) ||
|
|
211
|
+
aName.localeCompare(bName);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Re-append in sorted order
|
|
215
|
+
items.forEach(item => relationsList.appendChild(item));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Clear the add relation form
|
|
219
|
+
function clearAddForm() {
|
|
220
|
+
const verbKindSelect = document.getElementById('new-relation-verb-kind');
|
|
221
|
+
const instanceSelect = document.getElementById('new-relation-instance');
|
|
222
|
+
|
|
223
|
+
if (verbKindSelect) verbKindSelect.value = '';
|
|
224
|
+
if (instanceSelect) {
|
|
225
|
+
instanceSelect.innerHTML = '<option value="">Select instance...</option>';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Update instance dropdown based on selected verb:kind
|
|
230
|
+
// Uses embedded data instead of HTMX API calls
|
|
231
|
+
function updateInstanceOptions() {
|
|
232
|
+
const verbKindSelect = document.getElementById('new-relation-verb-kind');
|
|
233
|
+
const instanceSelect = document.getElementById('new-relation-instance');
|
|
234
|
+
const editor = document.querySelector('.relations-editor');
|
|
235
|
+
|
|
236
|
+
if (!verbKindSelect || !instanceSelect || !editor) return;
|
|
237
|
+
|
|
238
|
+
// Clear and reset
|
|
239
|
+
instanceSelect.innerHTML = '<option value="">Select instance...</option>';
|
|
240
|
+
|
|
241
|
+
const verbKind = verbKindSelect.value;
|
|
242
|
+
if (!verbKind) return;
|
|
243
|
+
|
|
244
|
+
// Parse kind from verb:kind
|
|
245
|
+
const parts = verbKind.split(':');
|
|
246
|
+
if (parts.length < 2) return;
|
|
247
|
+
const kind = parts.slice(1).join(':'); // Handle kinds that might contain colons
|
|
248
|
+
|
|
249
|
+
// Get instances from embedded data
|
|
250
|
+
const instancesData = JSON.parse(editor.dataset.instances || '{}');
|
|
251
|
+
const instances = instancesData[kind] || [];
|
|
252
|
+
|
|
253
|
+
// Populate dropdown
|
|
254
|
+
instances.forEach(name => {
|
|
255
|
+
const option = document.createElement('option');
|
|
256
|
+
option.value = name;
|
|
257
|
+
option.textContent = name;
|
|
258
|
+
instanceSelect.appendChild(option);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Escape HTML to prevent XSS
|
|
263
|
+
function escapeHtml(text) {
|
|
264
|
+
const div = document.createElement('div');
|
|
265
|
+
div.textContent = text;
|
|
266
|
+
return div.innerHTML;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Expose relation functions to global scope for onclick handlers
|
|
270
|
+
window.addRelation = addRelation;
|
|
271
|
+
window.removeRelation = removeRelation;
|
|
272
|
+
window.updateInstanceOptions = updateInstanceOptions;
|
|
273
|
+
|
|
274
|
+
// Copy YAML to clipboard
|
|
275
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
276
|
+
const copyButton = document.getElementById('copy-yaml');
|
|
277
|
+
if (copyButton) {
|
|
278
|
+
copyButton.addEventListener('click', function() {
|
|
279
|
+
const yamlContent = document.getElementById('yaml-content');
|
|
280
|
+
if (!yamlContent) return;
|
|
281
|
+
|
|
282
|
+
const yaml = yamlContent.textContent;
|
|
283
|
+
|
|
284
|
+
navigator.clipboard.writeText(yaml).then(function() {
|
|
285
|
+
// Show success feedback
|
|
286
|
+
const originalText = copyButton.innerHTML;
|
|
287
|
+
copyButton.innerHTML = '<i class="iconoir-check"></i> Copied!';
|
|
288
|
+
copyButton.classList.add('copied');
|
|
289
|
+
|
|
290
|
+
setTimeout(function() {
|
|
291
|
+
copyButton.innerHTML = originalText;
|
|
292
|
+
copyButton.classList.remove('copied');
|
|
293
|
+
}, 2000);
|
|
294
|
+
}).catch(function(err) {
|
|
295
|
+
console.error('Failed to copy YAML:', err);
|
|
296
|
+
// Fallback for older browsers
|
|
297
|
+
const textarea = document.createElement('textarea');
|
|
298
|
+
textarea.value = yaml;
|
|
299
|
+
textarea.style.position = 'fixed';
|
|
300
|
+
textarea.style.opacity = '0';
|
|
301
|
+
document.body.appendChild(textarea);
|
|
302
|
+
textarea.select();
|
|
303
|
+
document.execCommand('copy');
|
|
304
|
+
document.body.removeChild(textarea);
|
|
305
|
+
|
|
306
|
+
const originalText = copyButton.innerHTML;
|
|
307
|
+
copyButton.innerHTML = '<i class="iconoir-check"></i> Copied!';
|
|
308
|
+
copyButton.classList.add('copied');
|
|
309
|
+
|
|
310
|
+
setTimeout(function() {
|
|
311
|
+
copyButton.innerHTML = originalText;
|
|
312
|
+
copyButton.classList.remove('copied');
|
|
313
|
+
}, 2000);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Save YAML to source file (inline edit)
|
|
319
|
+
const saveButton = document.getElementById('save-yaml');
|
|
320
|
+
if (saveButton) {
|
|
321
|
+
saveButton.addEventListener('click', async function() {
|
|
322
|
+
const yamlContent = document.getElementById('yaml-content');
|
|
323
|
+
if (!yamlContent) return;
|
|
324
|
+
|
|
325
|
+
const yaml = yamlContent.textContent;
|
|
326
|
+
const kind = this.dataset.kind;
|
|
327
|
+
const name = this.dataset.name;
|
|
328
|
+
const contentHash = this.dataset.contentHash;
|
|
329
|
+
const originalText = this.innerHTML;
|
|
330
|
+
|
|
331
|
+
this.disabled = true;
|
|
332
|
+
this.innerHTML = '<i class="iconoir-refresh"></i> Saving...';
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const response = await fetch(`/api/v1/editor/kinds/${kind}/instances/${name}/save`, {
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: { 'Content-Type': 'application/json' },
|
|
338
|
+
body: JSON.stringify({ yaml: yaml, content_hash: contentHash })
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = await response.json();
|
|
342
|
+
if (result.success) {
|
|
343
|
+
this.innerHTML = '<i class="iconoir-check"></i> Saved!';
|
|
344
|
+
this.classList.add('saved');
|
|
345
|
+
setTimeout(() => {
|
|
346
|
+
this.innerHTML = originalText;
|
|
347
|
+
this.classList.remove('saved');
|
|
348
|
+
this.disabled = false;
|
|
349
|
+
}, 2000);
|
|
350
|
+
} else if (result.conflict) {
|
|
351
|
+
// Handle conflict with user-friendly message and reload option
|
|
352
|
+
this.innerHTML = originalText;
|
|
353
|
+
this.disabled = false;
|
|
354
|
+
showConflictError(result.error);
|
|
355
|
+
} else {
|
|
356
|
+
alert('Save failed: ' + result.error);
|
|
357
|
+
this.innerHTML = originalText;
|
|
358
|
+
this.disabled = false;
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
alert('Save failed: ' + err.message);
|
|
362
|
+
this.innerHTML = originalText;
|
|
363
|
+
this.disabled = false;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Show conflict error
|
|
369
|
+
function showConflictError(message) {
|
|
370
|
+
const yamlOutput = document.querySelector('.yaml-output');
|
|
371
|
+
if (!yamlOutput) {
|
|
372
|
+
alert(message);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check if error banner already exists
|
|
377
|
+
let errorBanner = document.getElementById('conflict-error');
|
|
378
|
+
if (!errorBanner) {
|
|
379
|
+
errorBanner = document.createElement('div');
|
|
380
|
+
errorBanner.id = 'conflict-error';
|
|
381
|
+
errorBanner.className = 'conflict-error';
|
|
382
|
+
// Insert before the article, not inside it
|
|
383
|
+
yamlOutput.parentNode.insertBefore(errorBanner, yamlOutput);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
errorBanner.innerHTML = `
|
|
387
|
+
<i class="iconoir-warning-triangle"></i>
|
|
388
|
+
<span>${escapeHtml(message)}</span>
|
|
389
|
+
`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Close overlay when pressing Escape
|
|
393
|
+
document.addEventListener('keydown', function(event) {
|
|
394
|
+
if (event.key === 'Escape') {
|
|
395
|
+
const overlay = document.getElementById('markdown-editor-overlay');
|
|
396
|
+
if (overlay && !overlay.classList.contains('hidden')) {
|
|
397
|
+
closeMarkdownEditor();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Track clicks in editor to show/hide code language selector
|
|
403
|
+
const editorRoot = document.getElementById('lexical-editor-root');
|
|
404
|
+
if (editorRoot) {
|
|
405
|
+
editorRoot.addEventListener('click', function(event) {
|
|
406
|
+
// Delay to let Lexical update selection
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
if (lexicalModule) {
|
|
409
|
+
updateCodeLanguageSelector();
|
|
410
|
+
}
|
|
411
|
+
}, 10);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Also track selection changes
|
|
415
|
+
document.addEventListener('selectionchange', function() {
|
|
416
|
+
if (lexicalModule && !document.querySelector('#markdown-editor-overlay.hidden')) {
|
|
417
|
+
updateCodeLanguageSelector();
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
});
|