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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +29 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +255 -0
  6. data/Rakefile +8 -0
  7. data/assets/.gitkeep +0 -0
  8. data/assets/Markymark.icns +0 -0
  9. data/assets/Markymark.iconset/icon_128x128.png +0 -0
  10. data/assets/Markymark.iconset/icon_128x128@2x.png +0 -0
  11. data/assets/Markymark.iconset/icon_16x16.png +0 -0
  12. data/assets/Markymark.iconset/icon_16x16@2x.png +0 -0
  13. data/assets/Markymark.iconset/icon_256x256.png +0 -0
  14. data/assets/Markymark.iconset/icon_256x256@2x.png +0 -0
  15. data/assets/Markymark.iconset/icon_32x32.png +0 -0
  16. data/assets/Markymark.iconset/icon_32x32@2x.png +0 -0
  17. data/assets/Markymark.iconset/icon_512x512.png +0 -0
  18. data/assets/Markymark.iconset/icon_512x512@2x.png +0 -0
  19. data/assets/README.md +3 -0
  20. data/assets/marky-mark-dj.jpg +0 -0
  21. data/assets/marky-mark-icon.png +0 -0
  22. data/assets/marky-mark-icon2.png +0 -0
  23. data/config.ru +19 -0
  24. data/docs/for_llms.md +141 -0
  25. data/docs/plans/2025-12-18-macos-app-installer-design.md +149 -0
  26. data/exe/markymark +5 -0
  27. data/lib/markymark/app_installer.rb +437 -0
  28. data/lib/markymark/cli.rb +497 -0
  29. data/lib/markymark/init_wizard.rb +186 -0
  30. data/lib/markymark/pumadev_manager.rb +194 -0
  31. data/lib/markymark/server_simple.rb +452 -0
  32. data/lib/markymark/version.rb +5 -0
  33. data/lib/markymark.rb +12 -0
  34. data/lib/public/css/style.css +350 -0
  35. data/lib/public/js/app.js +186 -0
  36. data/lib/public/js/theme.js +79 -0
  37. data/lib/public/js/tree.js +124 -0
  38. data/lib/views/browse.erb +225 -0
  39. data/lib/views/index.erb +37 -0
  40. data/lib/views/simple.erb +806 -0
  41. data/sig/markymark.rbs +4 -0
  42. metadata +242 -0
@@ -0,0 +1,124 @@
1
+ // File tree management
2
+ (function() {
3
+ let treeData = null;
4
+ let expandedFolders = new Set();
5
+
6
+ async function loadTree() {
7
+ try {
8
+ const response = await fetch('/api/tree');
9
+ treeData = await response.json();
10
+ renderTree();
11
+ } catch (error) {
12
+ console.error('Failed to load file tree:', error);
13
+ }
14
+ }
15
+
16
+ function renderTree() {
17
+ const container = document.getElementById('file-tree');
18
+ if (!container || !treeData) return;
19
+
20
+ container.innerHTML = '';
21
+ renderNode(treeData, container, 0);
22
+
23
+ // Auto-scroll to center the tree content
24
+ setTimeout(() => {
25
+ const scrollHeight = container.scrollHeight;
26
+ const clientHeight = container.clientHeight;
27
+ if (scrollHeight > clientHeight) {
28
+ // Scroll to approximately center
29
+ container.scrollTop = (scrollHeight - clientHeight) / 2;
30
+ }
31
+ }, 100);
32
+ }
33
+
34
+ function renderNode(node, container, depth) {
35
+ if (node.type === 'folder') {
36
+ renderFolder(node, container, depth);
37
+ } else {
38
+ renderFile(node, container, depth);
39
+ }
40
+ }
41
+
42
+ function renderFolder(folder, container, depth) {
43
+ const isExpanded = expandedFolders.has(folder.path);
44
+
45
+ const folderDiv = document.createElement('div');
46
+ folderDiv.className = 'tree-folder';
47
+ folderDiv.style.paddingLeft = `${depth * 12}px`;
48
+
49
+ const header = document.createElement('div');
50
+ header.className = 'tree-folder-header';
51
+ header.innerHTML = `
52
+ <span class="tree-icon">${isExpanded ? '▼' : '▶'}</span>
53
+ <span class="tree-label">${escapeHtml(folder.name)}</span>
54
+ `;
55
+
56
+ header.addEventListener('click', () => {
57
+ if (isExpanded) {
58
+ expandedFolders.delete(folder.path);
59
+ } else {
60
+ expandedFolders.add(folder.path);
61
+ }
62
+ renderTree();
63
+ });
64
+
65
+ folderDiv.appendChild(header);
66
+
67
+ if (isExpanded && folder.children) {
68
+ const childrenDiv = document.createElement('div');
69
+ childrenDiv.className = 'tree-children';
70
+
71
+ folder.children.forEach(child => {
72
+ renderNode(child, childrenDiv, depth + 1);
73
+ });
74
+
75
+ folderDiv.appendChild(childrenDiv);
76
+ }
77
+
78
+ container.appendChild(folderDiv);
79
+ }
80
+
81
+ function renderFile(file, container, depth) {
82
+ const fileDiv = document.createElement('div');
83
+ fileDiv.className = 'tree-file';
84
+ fileDiv.style.paddingLeft = `${depth * 12}px`;
85
+
86
+ // Check if this is the currently selected file
87
+ const urlParams = new URLSearchParams(window.location.search);
88
+ const currentFile = urlParams.get('file');
89
+ if (currentFile === file.path) {
90
+ fileDiv.classList.add('selected');
91
+ }
92
+
93
+ fileDiv.innerHTML = `
94
+ <span class="tree-icon">📄</span>
95
+ <span class="tree-label">${escapeHtml(file.name)}</span>
96
+ `;
97
+
98
+ fileDiv.addEventListener('click', () => {
99
+ window.location.href = `/view?file=${encodeURIComponent(file.path)}`;
100
+ });
101
+
102
+ container.appendChild(fileDiv);
103
+ }
104
+
105
+ function updateTree(newTreeData) {
106
+ treeData = newTreeData;
107
+ renderTree();
108
+ }
109
+
110
+ function escapeHtml(text) {
111
+ const div = document.createElement('div');
112
+ div.textContent = text;
113
+ return div.innerHTML;
114
+ }
115
+
116
+ // Initialize
117
+ loadTree();
118
+
119
+ // Export for use by app.js
120
+ window.MarkyTree = {
121
+ update: updateTree,
122
+ reload: loadTree
123
+ };
124
+ })();
@@ -0,0 +1,225 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Browse Directory - markymark</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica', 'Arial', sans-serif;
16
+ background-color: #f6f8fa;
17
+ padding: 32px;
18
+ }
19
+
20
+ .container {
21
+ max-width: 800px;
22
+ margin: 0 auto;
23
+ background-color: #ffffff;
24
+ border: 1px solid #d0d7de;
25
+ border-radius: 6px;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .header {
30
+ padding: 16px 24px;
31
+ background-color: #f6f8fa;
32
+ border-bottom: 1px solid #d0d7de;
33
+ }
34
+
35
+ .header h1 {
36
+ font-size: 20px;
37
+ font-weight: 600;
38
+ color: #24292f;
39
+ margin-bottom: 8px;
40
+ }
41
+
42
+ .current-path {
43
+ padding: 12px;
44
+ background-color: #f6f8fa;
45
+ border-radius: 6px;
46
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
47
+ font-size: 13px;
48
+ color: #24292f;
49
+ word-break: break-all;
50
+ }
51
+
52
+ .error {
53
+ padding: 12px;
54
+ background-color: #fff8c5;
55
+ border: 1px solid #d4a72c;
56
+ border-radius: 6px;
57
+ color: #7d6608;
58
+ margin-top: 12px;
59
+ }
60
+
61
+ .directory-list {
62
+ list-style: none;
63
+ }
64
+
65
+ .directory-item {
66
+ border-bottom: 1px solid #d0d7de;
67
+ }
68
+
69
+ .directory-item:last-child {
70
+ border-bottom: none;
71
+ }
72
+
73
+ .directory-link {
74
+ display: flex;
75
+ align-items: center;
76
+ padding: 12px 24px;
77
+ text-decoration: none;
78
+ color: #0969da;
79
+ transition: background-color 0.2s;
80
+ }
81
+
82
+ .directory-link:hover {
83
+ background-color: #f6f8fa;
84
+ }
85
+
86
+ .directory-link.parent {
87
+ color: #57606a;
88
+ font-weight: 600;
89
+ }
90
+
91
+ .directory-icon {
92
+ margin-right: 12px;
93
+ font-size: 16px;
94
+ }
95
+
96
+ .directory-name {
97
+ flex: 1;
98
+ font-size: 14px;
99
+ }
100
+
101
+ .actions {
102
+ padding: 16px 24px;
103
+ background-color: #f6f8fa;
104
+ border-top: 1px solid #d0d7de;
105
+ display: flex;
106
+ gap: 12px;
107
+ justify-content: space-between;
108
+ }
109
+
110
+ .btn {
111
+ padding: 8px 16px;
112
+ border-radius: 6px;
113
+ font-size: 14px;
114
+ font-weight: 600;
115
+ cursor: pointer;
116
+ text-decoration: none;
117
+ border: none;
118
+ transition: background-color 0.2s;
119
+ }
120
+
121
+ .btn-primary {
122
+ background-color: #0969da;
123
+ color: #ffffff;
124
+ }
125
+
126
+ .btn-primary:hover {
127
+ background-color: #0860ca;
128
+ }
129
+
130
+ .btn-secondary {
131
+ background-color: #ffffff;
132
+ color: #24292f;
133
+ border: 1px solid #d0d7de;
134
+ }
135
+
136
+ .btn-secondary:hover {
137
+ background-color: #f6f8fa;
138
+ }
139
+
140
+ .empty-state {
141
+ padding: 48px 24px;
142
+ text-align: center;
143
+ color: #57606a;
144
+ }
145
+
146
+ .empty-state-icon {
147
+ font-size: 48px;
148
+ margin-bottom: 16px;
149
+ }
150
+ </style>
151
+ </head>
152
+ <body>
153
+ <div class="container">
154
+ <div class="header">
155
+ <h1>Browse Directory</h1>
156
+ <div class="current-path">
157
+ <%= @browse_path %>
158
+ </div>
159
+ <% if @error %>
160
+ <div class="error">
161
+ <%= @error %>
162
+ </div>
163
+ <% end %>
164
+ </div>
165
+
166
+ <!-- Quick Shortcuts Section -->
167
+ <div style="padding: 16px 24px; background-color: #f6f8fa; border-bottom: 1px solid #d0d7de;">
168
+ <div style="font-size: 12px; font-weight: 600; color: #57606a; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Quick Shortcuts</div>
169
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
170
+ <a href="/browse-dir?path=<%= CGI.escape(File.expand_path('~')) %>" class="btn btn-secondary" style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px;">
171
+ <span>🏠</span>
172
+ <span>Home</span>
173
+ </a>
174
+ <a href="/browse-dir?path=<%= CGI.escape(File.expand_path('~/Downloads')) %>" class="btn btn-secondary" style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px;">
175
+ <span>📥</span>
176
+ <span>Downloads</span>
177
+ </a>
178
+ <a href="/browse-dir?path=<%= CGI.escape(File.expand_path('~/work')) %>" class="btn btn-secondary" style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px;">
179
+ <span>💼</span>
180
+ <span>Work</span>
181
+ </a>
182
+ </div>
183
+ </div>
184
+
185
+ <ul class="directory-list">
186
+ <!-- Parent directory link -->
187
+ <% if @browse_path != '/' %>
188
+ <li class="directory-item">
189
+ <a href="/browse-dir?path=<%= CGI.escape(@parent_dir) %>" class="directory-link parent">
190
+ <span class="directory-icon">↑</span>
191
+ <span class="directory-name">..</span>
192
+ </a>
193
+ </li>
194
+ <% end %>
195
+
196
+ <!-- Subdirectories -->
197
+ <% if @directories.empty? %>
198
+ <li class="directory-item">
199
+ <div class="empty-state">
200
+ <div class="empty-state-icon">📂</div>
201
+ <p>No subdirectories found</p>
202
+ </div>
203
+ </li>
204
+ <% else %>
205
+ <% @directories.each do |dir| %>
206
+ <li class="directory-item">
207
+ <a href="/browse-dir?path=<%= CGI.escape(File.join(@browse_path, dir)) %>" class="directory-link">
208
+ <span class="directory-icon">📁</span>
209
+ <span class="directory-name"><%= dir %></span>
210
+ </a>
211
+ </li>
212
+ <% end %>
213
+ <% end %>
214
+ </ul>
215
+
216
+ <div class="actions">
217
+ <a href="/?dir=<%= CGI.escape(@current_dir) %>" class="btn btn-secondary">Cancel</a>
218
+ <form action="/change-dir" method="post" style="display: inline;">
219
+ <input type="hidden" name="path" value="<%= @browse_path %>">
220
+ <button type="submit" class="btn btn-primary">Use This Directory</button>
221
+ </form>
222
+ </div>
223
+ </div>
224
+ </body>
225
+ </html>
@@ -0,0 +1,37 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>markymark</title>
7
+ <link rel="stylesheet" href="/css/style.css<%= cache_bust %>">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" id="highlight-light">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" id="highlight-dark" disabled>
10
+ </head>
11
+ <body>
12
+ <header class="header">
13
+ <h1 class="logo">markymark</h1>
14
+ <button class="theme-toggle" id="theme-toggle" aria-label="Toggle theme">
15
+ <span class="theme-icon">🌙</span>
16
+ </button>
17
+ <div class="current-path" id="current-path"></div>
18
+ </header>
19
+
20
+ <div class="container">
21
+ <aside class="sidebar">
22
+ <div id="file-tree" class="file-tree"></div>
23
+ </aside>
24
+
25
+ <main class="content">
26
+ <div id="markdown-content" class="markdown-body"></div>
27
+ </main>
28
+ </div>
29
+
30
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.1/marked.min.js"></script>
31
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
32
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
33
+ <script src="/js/theme.js"></script>
34
+ <script src="/js/tree.js"></script>
35
+ <script src="/js/app.js"></script>
36
+ </body>
37
+ </html>