markymark 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE.txt +21 -0
- data/README.md +255 -0
- data/Rakefile +8 -0
- data/assets/.gitkeep +0 -0
- data/assets/Markymark.icns +0 -0
- data/assets/Markymark.iconset/icon_128x128.png +0 -0
- data/assets/Markymark.iconset/icon_128x128@2x.png +0 -0
- data/assets/Markymark.iconset/icon_16x16.png +0 -0
- data/assets/Markymark.iconset/icon_16x16@2x.png +0 -0
- data/assets/Markymark.iconset/icon_256x256.png +0 -0
- data/assets/Markymark.iconset/icon_256x256@2x.png +0 -0
- data/assets/Markymark.iconset/icon_32x32.png +0 -0
- data/assets/Markymark.iconset/icon_32x32@2x.png +0 -0
- data/assets/Markymark.iconset/icon_512x512.png +0 -0
- data/assets/Markymark.iconset/icon_512x512@2x.png +0 -0
- data/assets/README.md +3 -0
- data/assets/marky-mark-dj.jpg +0 -0
- data/assets/marky-mark-icon.png +0 -0
- data/assets/marky-mark-icon2.png +0 -0
- data/config.ru +19 -0
- data/docs/for_llms.md +141 -0
- data/docs/plans/2025-12-18-macos-app-installer-design.md +149 -0
- data/exe/markymark +5 -0
- data/lib/markymark/app_installer.rb +437 -0
- data/lib/markymark/cli.rb +497 -0
- data/lib/markymark/init_wizard.rb +186 -0
- data/lib/markymark/pumadev_manager.rb +194 -0
- data/lib/markymark/server_simple.rb +452 -0
- data/lib/markymark/version.rb +5 -0
- data/lib/markymark.rb +12 -0
- data/lib/public/css/style.css +350 -0
- data/lib/public/js/app.js +186 -0
- data/lib/public/js/theme.js +79 -0
- data/lib/public/js/tree.js +124 -0
- data/lib/views/browse.erb +225 -0
- data/lib/views/index.erb +37 -0
- data/lib/views/simple.erb +806 -0
- data/sig/markymark.rbs +4 -0
- metadata +242 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/* CSS Variables for theming */
|
|
2
|
+
:root {
|
|
3
|
+
--bg-primary: #ffffff;
|
|
4
|
+
--bg-secondary: #f6f8fa;
|
|
5
|
+
--bg-tertiary: #f0f0f0;
|
|
6
|
+
--text-primary: #24292f;
|
|
7
|
+
--text-secondary: #57606a;
|
|
8
|
+
--border: #d0d7de;
|
|
9
|
+
--link: #0969da;
|
|
10
|
+
--link-hover: #0550ae;
|
|
11
|
+
--code-bg: #f6f8fa;
|
|
12
|
+
--sidebar-bg: #ffffff;
|
|
13
|
+
--header-bg: #24292f;
|
|
14
|
+
--header-text: #ffffff;
|
|
15
|
+
--selected-bg: #ddf4ff;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[data-theme="dark"] {
|
|
19
|
+
--bg-primary: #0d1117;
|
|
20
|
+
--bg-secondary: #161b22;
|
|
21
|
+
--bg-tertiary: #21262d;
|
|
22
|
+
--text-primary: #c9d1d9;
|
|
23
|
+
--text-secondary: #8b949e;
|
|
24
|
+
--border: #30363d;
|
|
25
|
+
--link: #58a6ff;
|
|
26
|
+
--link-hover: #79c0ff;
|
|
27
|
+
--code-bg: #161b22;
|
|
28
|
+
--sidebar-bg: #0d1117;
|
|
29
|
+
--header-bg: #161b22;
|
|
30
|
+
--header-text: #c9d1d9;
|
|
31
|
+
--selected-bg: #1f6feb20;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
html, body {
|
|
41
|
+
height: 100vh;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
body {
|
|
48
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
|
|
49
|
+
background-color: var(--bg-primary);
|
|
50
|
+
color: var(--text-primary);
|
|
51
|
+
line-height: 1.6;
|
|
52
|
+
transition: background-color 0.3s, color 0.3s;
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Header */
|
|
58
|
+
.header {
|
|
59
|
+
background-color: var(--header-bg);
|
|
60
|
+
color: var(--header-text);
|
|
61
|
+
padding: 1rem 1.5rem;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 1rem;
|
|
65
|
+
border-bottom: 1px solid var(--border);
|
|
66
|
+
flex-shrink: 0; /* Header doesn't shrink */
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.logo {
|
|
70
|
+
font-size: 1.5rem;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
margin: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.theme-toggle {
|
|
76
|
+
background: none;
|
|
77
|
+
border: 1px solid var(--border);
|
|
78
|
+
border-radius: 6px;
|
|
79
|
+
padding: 0.5rem;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
font-size: 1.2rem;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
transition: background-color 0.2s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.theme-toggle:hover {
|
|
89
|
+
background-color: var(--bg-tertiary);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.current-path {
|
|
93
|
+
flex: 1;
|
|
94
|
+
font-size: 0.9rem;
|
|
95
|
+
color: var(--text-secondary);
|
|
96
|
+
font-family: monospace;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Main container */
|
|
100
|
+
.container {
|
|
101
|
+
display: flex;
|
|
102
|
+
flex: 1; /* Takes remaining space after header */
|
|
103
|
+
min-height: 0; /* Critical: allows flex children to shrink below content size */
|
|
104
|
+
overflow: hidden;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Sidebar */
|
|
108
|
+
.sidebar {
|
|
109
|
+
width: 300px;
|
|
110
|
+
min-width: 300px;
|
|
111
|
+
max-width: 300px;
|
|
112
|
+
background-color: var(--sidebar-bg);
|
|
113
|
+
border-right: 1px solid var(--border);
|
|
114
|
+
flex-shrink: 0;
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
min-height: 0; /* Critical: allows file-tree to respect height constraint */
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.file-tree {
|
|
121
|
+
user-select: none;
|
|
122
|
+
padding: 1rem;
|
|
123
|
+
flex: 1;
|
|
124
|
+
min-height: 0; /* Allows content to shrink */
|
|
125
|
+
overflow-y: scroll !important; /* Force vertical scrollbar to always show */
|
|
126
|
+
overflow-x: auto; /* Show horizontal scrollbar if needed */
|
|
127
|
+
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.tree-folder,
|
|
131
|
+
.tree-file {
|
|
132
|
+
padding: 0.4rem 0.5rem;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
border-radius: 4px;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 0.5rem;
|
|
138
|
+
transition: background-color 0.15s;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.tree-folder:hover,
|
|
142
|
+
.tree-file:hover {
|
|
143
|
+
background-color: var(--bg-secondary);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.tree-file.selected {
|
|
147
|
+
background-color: var(--selected-bg);
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.tree-folder-header {
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 0.5rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.tree-icon {
|
|
158
|
+
flex-shrink: 0;
|
|
159
|
+
font-size: 0.9rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.tree-label {
|
|
163
|
+
flex: 1;
|
|
164
|
+
overflow: hidden;
|
|
165
|
+
text-overflow: ellipsis;
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Content area */
|
|
170
|
+
.content {
|
|
171
|
+
flex: 1;
|
|
172
|
+
overflow-y: auto;
|
|
173
|
+
padding: 2rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Markdown styles (GitHub-flavored) */
|
|
177
|
+
.markdown-body {
|
|
178
|
+
max-width: 980px;
|
|
179
|
+
margin: 0 auto;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.markdown-body h1,
|
|
183
|
+
.markdown-body h2,
|
|
184
|
+
.markdown-body h3,
|
|
185
|
+
.markdown-body h4,
|
|
186
|
+
.markdown-body h5,
|
|
187
|
+
.markdown-body h6 {
|
|
188
|
+
margin-top: 24px;
|
|
189
|
+
margin-bottom: 16px;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
line-height: 1.25;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.markdown-body h1 {
|
|
195
|
+
font-size: 2em;
|
|
196
|
+
border-bottom: 1px solid var(--border);
|
|
197
|
+
padding-bottom: 0.3em;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.markdown-body h2 {
|
|
201
|
+
font-size: 1.5em;
|
|
202
|
+
border-bottom: 1px solid var(--border);
|
|
203
|
+
padding-bottom: 0.3em;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.markdown-body h3 {
|
|
207
|
+
font-size: 1.25em;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.markdown-body p {
|
|
211
|
+
margin-top: 0;
|
|
212
|
+
margin-bottom: 16px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.markdown-body a {
|
|
216
|
+
color: var(--link);
|
|
217
|
+
text-decoration: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.markdown-body a:hover {
|
|
221
|
+
color: var(--link-hover);
|
|
222
|
+
text-decoration: underline;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.markdown-body code {
|
|
226
|
+
background-color: var(--code-bg);
|
|
227
|
+
padding: 0.2em 0.4em;
|
|
228
|
+
margin: 0;
|
|
229
|
+
font-size: 85%;
|
|
230
|
+
border-radius: 6px;
|
|
231
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.markdown-body pre {
|
|
235
|
+
background-color: var(--code-bg);
|
|
236
|
+
padding: 16px;
|
|
237
|
+
overflow: auto;
|
|
238
|
+
font-size: 85%;
|
|
239
|
+
line-height: 1.45;
|
|
240
|
+
border-radius: 6px;
|
|
241
|
+
margin-bottom: 16px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.markdown-body pre code {
|
|
245
|
+
background-color: transparent;
|
|
246
|
+
padding: 0;
|
|
247
|
+
border-radius: 0;
|
|
248
|
+
font-size: 100%;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.markdown-body blockquote {
|
|
252
|
+
padding: 0 1em;
|
|
253
|
+
color: var(--text-secondary);
|
|
254
|
+
border-left: 0.25em solid var(--border);
|
|
255
|
+
margin-bottom: 16px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.markdown-body ul,
|
|
259
|
+
.markdown-body ol {
|
|
260
|
+
padding-left: 2em;
|
|
261
|
+
margin-bottom: 16px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.markdown-body li {
|
|
265
|
+
margin-bottom: 0.25em;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.markdown-body table {
|
|
269
|
+
border-spacing: 0;
|
|
270
|
+
border-collapse: collapse;
|
|
271
|
+
margin-bottom: 16px;
|
|
272
|
+
width: 100%;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.markdown-body table tr {
|
|
276
|
+
background-color: var(--bg-primary);
|
|
277
|
+
border-top: 1px solid var(--border);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.markdown-body table tr:nth-child(2n) {
|
|
281
|
+
background-color: var(--bg-secondary);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.markdown-body table th,
|
|
285
|
+
.markdown-body table td {
|
|
286
|
+
padding: 6px 13px;
|
|
287
|
+
border: 1px solid var(--border);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.markdown-body table th {
|
|
291
|
+
font-weight: 600;
|
|
292
|
+
background-color: var(--bg-secondary);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.markdown-body img {
|
|
296
|
+
max-width: 100%;
|
|
297
|
+
height: auto;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.markdown-body hr {
|
|
301
|
+
height: 0.25em;
|
|
302
|
+
padding: 0;
|
|
303
|
+
margin: 24px 0;
|
|
304
|
+
background-color: var(--border);
|
|
305
|
+
border: 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Mermaid diagrams */
|
|
309
|
+
.mermaid-diagram {
|
|
310
|
+
margin: 16px 0;
|
|
311
|
+
text-align: center;
|
|
312
|
+
background-color: var(--bg-secondary);
|
|
313
|
+
padding: 16px;
|
|
314
|
+
border-radius: 6px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.mermaid-error {
|
|
318
|
+
background-color: #ff000020;
|
|
319
|
+
border: 1px solid #ff0000;
|
|
320
|
+
padding: 16px;
|
|
321
|
+
border-radius: 6px;
|
|
322
|
+
margin: 16px 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Messages */
|
|
326
|
+
.message {
|
|
327
|
+
padding: 2rem;
|
|
328
|
+
text-align: center;
|
|
329
|
+
color: var(--text-secondary);
|
|
330
|
+
font-size: 1.1rem;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Scrollbar styling */
|
|
334
|
+
::-webkit-scrollbar {
|
|
335
|
+
width: 12px;
|
|
336
|
+
height: 12px;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
::-webkit-scrollbar-track {
|
|
340
|
+
background: var(--bg-primary);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
::-webkit-scrollbar-thumb {
|
|
344
|
+
background: var(--border);
|
|
345
|
+
border-radius: 6px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
::-webkit-scrollbar-thumb:hover {
|
|
349
|
+
background: var(--text-secondary);
|
|
350
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Main application logic
|
|
2
|
+
(function() {
|
|
3
|
+
let currentFile = null;
|
|
4
|
+
let eventSource = null;
|
|
5
|
+
|
|
6
|
+
function init() {
|
|
7
|
+
// Configure marked.js for GFM
|
|
8
|
+
marked.setOptions({
|
|
9
|
+
gfm: true,
|
|
10
|
+
breaks: true,
|
|
11
|
+
headerIds: true,
|
|
12
|
+
mangle: false
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Configure mermaid
|
|
16
|
+
mermaid.initialize({
|
|
17
|
+
startOnLoad: false,
|
|
18
|
+
theme: window.MarkyTheme && window.MarkyTheme.current() === 'dark' ? 'dark' : 'default'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Load file from URL or default
|
|
22
|
+
loadInitialFile();
|
|
23
|
+
|
|
24
|
+
// Set up SSE connection
|
|
25
|
+
connectSSE();
|
|
26
|
+
|
|
27
|
+
// Handle browser back/forward
|
|
28
|
+
window.addEventListener('popstate', () => {
|
|
29
|
+
loadFileFromURL();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function loadInitialFile() {
|
|
34
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
35
|
+
const fileParam = urlParams.get('file');
|
|
36
|
+
|
|
37
|
+
if (fileParam) {
|
|
38
|
+
await loadFile(fileParam, false);
|
|
39
|
+
} else {
|
|
40
|
+
// Load default file
|
|
41
|
+
await loadDefaultFile();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function loadDefaultFile() {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch('/api/default-file');
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
|
|
50
|
+
if (data.file) {
|
|
51
|
+
// Update URL and load file
|
|
52
|
+
const url = new URL(window.location);
|
|
53
|
+
url.searchParams.set('file', data.file);
|
|
54
|
+
window.history.replaceState({}, '', url);
|
|
55
|
+
await loadFile(data.file, false);
|
|
56
|
+
} else {
|
|
57
|
+
showMessage('No markdown files found in this directory.');
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Failed to load default file:', error);
|
|
61
|
+
showMessage('Failed to load default file.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function loadFileFromURL() {
|
|
66
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
67
|
+
const fileParam = urlParams.get('file');
|
|
68
|
+
if (fileParam) {
|
|
69
|
+
await loadFile(fileParam, false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function loadFile(filePath, updateHistory = true) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`/api/content?file=${encodeURIComponent(filePath)}`);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error('Failed to load file');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data = await response.json();
|
|
81
|
+
currentFile = data.path;
|
|
82
|
+
|
|
83
|
+
// Update URL if needed
|
|
84
|
+
if (updateHistory) {
|
|
85
|
+
const url = new URL(window.location);
|
|
86
|
+
url.searchParams.set('file', filePath);
|
|
87
|
+
window.history.pushState({}, '', url);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Update current path display
|
|
91
|
+
const pathDisplay = document.getElementById('current-path');
|
|
92
|
+
if (pathDisplay) {
|
|
93
|
+
pathDisplay.textContent = filePath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Render markdown
|
|
97
|
+
renderMarkdown(data.content);
|
|
98
|
+
|
|
99
|
+
// Update tree selection
|
|
100
|
+
if (window.MarkyTree) {
|
|
101
|
+
window.MarkyTree.reload();
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Failed to load file:', error);
|
|
105
|
+
showMessage('Failed to load file: ' + filePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderMarkdown(markdown) {
|
|
110
|
+
const container = document.getElementById('markdown-content');
|
|
111
|
+
if (!container) return;
|
|
112
|
+
|
|
113
|
+
// Convert markdown to HTML
|
|
114
|
+
const html = marked.parse(markdown);
|
|
115
|
+
container.innerHTML = html;
|
|
116
|
+
|
|
117
|
+
// Highlight code blocks
|
|
118
|
+
container.querySelectorAll('pre code').forEach((block) => {
|
|
119
|
+
hljs.highlightElement(block);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Render Mermaid diagrams
|
|
123
|
+
container.querySelectorAll('code.language-mermaid').forEach(async (block, index) => {
|
|
124
|
+
const code = block.textContent;
|
|
125
|
+
const id = `mermaid-${Date.now()}-${index}`;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const { svg } = await mermaid.render(id, code);
|
|
129
|
+
const wrapper = document.createElement('div');
|
|
130
|
+
wrapper.className = 'mermaid-diagram';
|
|
131
|
+
wrapper.innerHTML = svg;
|
|
132
|
+
block.parentElement.replaceWith(wrapper);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('Mermaid rendering error:', error);
|
|
135
|
+
block.parentElement.classList.add('mermaid-error');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function showMessage(message) {
|
|
141
|
+
const container = document.getElementById('markdown-content');
|
|
142
|
+
if (container) {
|
|
143
|
+
container.innerHTML = `<div class="message">${escapeHtml(message)}</div>`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function connectSSE() {
|
|
148
|
+
eventSource = new EventSource('/stream');
|
|
149
|
+
|
|
150
|
+
eventSource.addEventListener('file_changed', (event) => {
|
|
151
|
+
const data = JSON.parse(event.data);
|
|
152
|
+
if (data.path === currentFile) {
|
|
153
|
+
// Current file was modified, re-render
|
|
154
|
+
renderMarkdown(data.content);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
eventSource.addEventListener('tree_updated', (event) => {
|
|
159
|
+
const tree = JSON.parse(event.data);
|
|
160
|
+
if (window.MarkyTree) {
|
|
161
|
+
window.MarkyTree.update(tree);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
eventSource.onerror = (error) => {
|
|
166
|
+
console.error('SSE error:', error);
|
|
167
|
+
// Reconnect after delay
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
connectSSE();
|
|
170
|
+
}, 5000);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function escapeHtml(text) {
|
|
175
|
+
const div = document.createElement('div');
|
|
176
|
+
div.textContent = text;
|
|
177
|
+
return div.innerHTML;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Initialize on DOM load
|
|
181
|
+
if (document.readyState === 'loading') {
|
|
182
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
183
|
+
} else {
|
|
184
|
+
init();
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Theme management for dark/light mode
|
|
2
|
+
(function() {
|
|
3
|
+
const THEME_KEY = 'markymark-theme';
|
|
4
|
+
const LIGHT = 'light';
|
|
5
|
+
const DARK = 'dark';
|
|
6
|
+
|
|
7
|
+
let currentTheme = LIGHT;
|
|
8
|
+
|
|
9
|
+
function initTheme() {
|
|
10
|
+
// Check localStorage first, then system preference
|
|
11
|
+
const savedTheme = localStorage.getItem(THEME_KEY);
|
|
12
|
+
if (savedTheme) {
|
|
13
|
+
currentTheme = savedTheme;
|
|
14
|
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
15
|
+
currentTheme = DARK;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
applyTheme(currentTheme);
|
|
19
|
+
setupToggle();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyTheme(theme) {
|
|
23
|
+
currentTheme = theme;
|
|
24
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
25
|
+
|
|
26
|
+
// Toggle highlight.js stylesheets
|
|
27
|
+
const lightStyle = document.getElementById('highlight-light');
|
|
28
|
+
const darkStyle = document.getElementById('highlight-dark');
|
|
29
|
+
|
|
30
|
+
if (theme === DARK) {
|
|
31
|
+
lightStyle.disabled = true;
|
|
32
|
+
darkStyle.disabled = false;
|
|
33
|
+
} else {
|
|
34
|
+
lightStyle.disabled = false;
|
|
35
|
+
darkStyle.disabled = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Update toggle button icon
|
|
39
|
+
const themeIcon = document.querySelector('.theme-icon');
|
|
40
|
+
if (themeIcon) {
|
|
41
|
+
themeIcon.textContent = theme === DARK ? '☀️' : '🌙';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Save to localStorage
|
|
45
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toggleTheme() {
|
|
49
|
+
const newTheme = currentTheme === LIGHT ? DARK : LIGHT;
|
|
50
|
+
applyTheme(newTheme);
|
|
51
|
+
|
|
52
|
+
// Re-highlight code blocks with new theme
|
|
53
|
+
if (window.hljs) {
|
|
54
|
+
document.querySelectorAll('pre code').forEach((block) => {
|
|
55
|
+
hljs.highlightElement(block);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setupToggle() {
|
|
61
|
+
const toggle = document.getElementById('theme-toggle');
|
|
62
|
+
if (toggle) {
|
|
63
|
+
toggle.addEventListener('click', toggleTheme);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize on DOM load
|
|
68
|
+
if (document.readyState === 'loading') {
|
|
69
|
+
document.addEventListener('DOMContentLoaded', initTheme);
|
|
70
|
+
} else {
|
|
71
|
+
initTheme();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Export for use by other scripts
|
|
75
|
+
window.MarkyTheme = {
|
|
76
|
+
current: () => currentTheme,
|
|
77
|
+
toggle: toggleTheme
|
|
78
|
+
};
|
|
79
|
+
})();
|