Tests/temp.md
... ...
@@ -1,305 +0,0 @@
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" />
6
- <title>Browser Git + Markdown Hierarchy (vanilla)</title>
7
- <link rel="manifest" href="manifest.json" />
8
- <style>
9
- :root{--bg:#0f1720;--panel:#0b1220;--muted:#9aa4b2;--accent:#4f46e5;--card:#0b1226}
10
- html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;background:linear-gradient(180deg,#071025 0%,#071827 100%);color:#e6eef6}
11
- .app{display:grid;grid-template-columns:260px 1fr 420px;gap:12px;padding:12px;height:100vh;box-sizing:border-box}
12
- .panel{background:var(--panel);border-radius:8px;padding:10px;box-shadow:0 6px 18px rgba(2,6,23,.6);overflow:auto}
13
- header{display:flex;gap:12px;align-items:center;margin-bottom:8px}
14
- header h1{font-size:14px;margin:0}
15
- .tree{font-size:13px}
16
- .node{padding:6px;border-radius:6px;cursor:pointer;color:var(--muted)}
17
- .node:hover{background:#0f1a2b;color:#fff}
18
- .node.active{background:linear-gradient(90deg,var(--accent),#2dd4bf);color:#021124}
19
- .toolbar{display:flex;gap:6px;margin-bottom:8px}
20
- button{background:#0b1226;border:1px solid rgba(255,255,255,.04);color:#dbeafe;padding:6px 8px;border-radius:6px;cursor:pointer}
21
- textarea{width:100%;height:60vh;background:#071226;color:#e6eef6;border:1px solid rgba(255,255,255,.04);padding:8px;border-radius:6px;font-family:monospace;font-size:13px}
22
- .preview{background:#071226;padding:10px;border-radius:6px;height:60vh;overflow:auto}
23
- .meta{display:flex;gap:8px;align-items:center;margin-bottom:8px}
24
- input[type=text]{padding:6px;border-radius:6px;border:1px solid rgba(255,255,255,.04);background:#071226;color:#e6eef6}
25
- .small{font-size:12px;color:var(--muted)}
26
- footer{margin-top:8px;font-size:12px;color:var(--muted)}
27
- </style>
28
-</head>
29
-<body>
30
- <div class="app">
31
- <div class="panel" id="left">
32
- <header>
33
- <h1>Repo</h1>
34
- <div class="small" id="status">init...</div>
35
- </header>
36
- <div class="toolbar">
37
- <button id="newFile">New file</button>
38
- <button id="newFolder">New folder</button>
39
- <button id="refreshTree">Refresh</button>
40
- </div>
41
- <div class="tree" id="tree"></div>
42
- <footer class="small">Files stored in browser IndexedDB via LightningFS</footer>
43
- </div>
44
-
45
- <div class="panel" id="center">
46
- <div class="meta">
47
- <input id="filepath" type="text" placeholder="objects/example.md" style="flex:1" />
48
- <input id="commitMsg" type="text" placeholder="Commit message" style="width:260px" />
49
- <button id="saveCommit">Save & Commit</button>
50
- </div>
51
- <textarea id="editor" placeholder="File content (Markdown or YAML/JSON)"></textarea>
52
- <div style="display:flex;gap:8px;margin-top:8px">
53
- <button id="initRepo">Init Repo</button>
54
- <button id="logBtn">Log</button>
55
- <button id="setRemote">Set Remote</button>
56
- <button id="pushBtn">Push</button>
57
- <button id="pullBtn">Pull</button>
58
- </div>
59
- <pre id="log" class="small" style="margin-top:8px;white-space:pre-wrap"></pre>
60
- </div>
61
-
62
- <div class="panel" id="right">
63
- <header><h1>Markdown Preview</h1></header>
64
- <div class="preview" id="preview">Nothing selected</div>
65
- </div>
66
- </div>
67
-
68
- <script type="module">
69
- import LightningFS from 'https://unpkg.com/@isomorphic-git/lightning-fs@4.0.1?module';
70
- import * as git from 'https://unpkg.com/isomorphic-git@1.20.0?module';
71
- import { marked } from 'https://cdn.jsdelivr.net/npm/marked@5.1.1/+esm';
72
-
73
- // Basic globals
74
- const repoDir = '/repo';
75
- const fs = new LightningFS('browser-fs', { wipe: false });
76
- git.plugins.set('fs', fs);
77
-
78
- const pfs = fs.promises;
79
- const treeEl = document.getElementById('tree');
80
- const editor = document.getElementById('editor');
81
- const preview = document.getElementById('preview');
82
- const filepathInput = document.getElementById('filepath');
83
- const commitMsgInput = document.getElementById('commitMsg');
84
- const statusEl = document.getElementById('status');
85
- const logEl = document.getElementById('log');
86
-
87
- // Utility: ensure repo dir exists
88
- async function ensureRepoDir() {
89
- try { await pfs.stat(repoDir); } catch (e) { await pfs.mkdir(repoDir); }
90
- }
91
-
92
- // Initialize git repo if not present
93
- async function initRepo() {
94
- status('initializing repo...');
95
- await ensureRepoDir();
96
- try {
97
- const isRepo = await git.isRepo({ fs, dir: repoDir });
98
- if (!isRepo) {
99
- await git.init({ fs, dir: repoDir, defaultBranch: 'main' });
100
- status('repo initialized');
101
- } else {
102
- status('repo already initialized');
103
- }
104
- } catch (e) {
105
- console.error(e);
106
- status('init error: ' + e.message);
107
- }
108
- await refreshTree();
109
- }
110
-
111
- // Read directory recursively and build tree
112
- async function readTree(dir = repoDir) {
113
- const items = [];
114
- try {
115
- const names = await pfs.readdir(dir);
116
- for (const name of names) {
117
- const path = dir + '/' + name;
118
- const st = await pfs.stat(path);
119
- if (st && st.type === 'directory') {
120
- items.push({ name, path, type: 'dir', children: await readTree(path) });
121
- } else {
122
- items.push({ name, path, type: 'file' });
123
- }
124
- }
125
- } catch (e) {
126
- // ignore
127
- }
128
- return items;
129
- }
130
-
131
- function renderTree(nodes, container) {
132
- container.innerHTML = '';
133
- for (const node of nodes) {
134
- const div = document.createElement('div');
135
- div.className = 'node';
136
- div.textContent = node.name;
137
- div.title = node.path;
138
- div.onclick = async (ev) => {
139
- ev.stopPropagation();
140
- document.querySelectorAll('.node').forEach(n=>n.classList.remove('active'));
141
- div.classList.add('active');
142
- if (node.type === 'file') {
143
- const rel = node.path.replace(repoDir + '/', '');
144
- filepathInput.value = rel;
145
- const data = await pfs.readFile(node.path, { encoding: 'utf8' }).catch(()=>'');
146
- editor.value = data;
147
- preview.innerHTML = marked.parse(data || '');
148
- } else {
149
- // toggle expand: show children inline
150
- if (div.nextSibling && div.nextSibling.classList && div.nextSibling.classList.contains('children')) {
151
- div.nextSibling.remove();
152
- } else {
153
- const childWrap = document.createElement('div');
154
- childWrap.className = 'children';
155
- childWrap.style.paddingLeft = '12px';
156
- renderTree(node.children, childWrap);
157
- div.after(childWrap);
158
- }
159
- }
160
- };
161
- container.appendChild(div);
162
- }
163
- }
164
-
165
- async function refreshTree() {
166
- const nodes = await readTree();
167
- renderTree(nodes, treeEl);
168
- status('tree refreshed');
169
- }
170
-
171
- // Save file to fs and stage/commit
172
- async function saveAndCommit() {
173
- const rel = filepathInput.value.trim();
174
- if (!rel) { alert('Enter a filepath'); return; }
175
- const full = repoDir + '/' + rel;
176
- // ensure parent dirs
177
- const parts = full.split('/');
178
- for (let i = 2; i <= parts.length - 1; i++) {
179
- const p = parts.slice(0, i).join('/');
180
- try { await pfs.stat(p); } catch (e) { await pfs.mkdir(p); }
181
- }
182
- await pfs.writeFile(full, editor.value, { encoding: 'utf8' });
183
- await git.add({ fs, dir: repoDir, filepath: rel });
184
- const message = commitMsgInput.value.trim() || 'Edit ' + rel;
185
- try {
186
- const sha = await git.commit({
187
- fs, dir: repoDir,
188
- author: { name: 'Browser User', email: 'user@local' },
189
- message
190
- });
191
- status('committed ' + sha.slice(0,7));
192
- await refreshTree();
193
- log('Committed: ' + sha);
194
- } catch (e) {
195
- console.error(e);
196
- status('commit error: ' + e.message);
197
- }
198
- }
199
-
200
- // Show log
201
- async function showLog() {
202
- try {
203
- const commits = await git.log({ fs, dir: repoDir, depth: 20 });
204
- logEl.textContent = commits.map(c => `${c.oid.slice(0,7)} ${c.commit.message} — ${c.commit.author.name}`).join('\n\n');
205
- } catch (e) {
206
- logEl.textContent = 'Log error: ' + e.message;
207
- }
208
- }
209
-
210
- // Set remote (store in localStorage)
211
- function setRemote() {
212
- const url = prompt('Remote URL (HTTPS, e.g. https://github.com/you/repo.git)');
213
- if (url) {
214
- localStorage.setItem('remoteUrl', url);
215
- status('remote set');
216
- }
217
- }
218
-
219
- // Push (prompts for token)
220
- async function push() {
221
- const remote = localStorage.getItem('remoteUrl');
222
- if (!remote) { alert('Set remote first'); return; }
223
- const token = prompt('Personal access token (will not be stored). For GitHub use token as password and username "x-access-token" or "token".');
224
- if (!token) return;
225
- status('pushing...');
226
- try {
227
- await git.addRemote({ fs, dir: repoDir, remote: 'origin', url: remote }).catch(()=>{});
228
- await git.push({
229
- fs, http: git.http, dir: repoDir,
230
- remote: 'origin', ref: 'main',
231
- onAuth: () => ({ username: 'x-access-token', password: token })
232
- });
233
- status('push complete');
234
- } catch (e) {
235
- console.error(e);
236
- status('push failed: ' + e.message);
237
- }
238
- }
239
-
240
- // Pull (prompts for token)
241
- async function pull() {
242
- const remote = localStorage.getItem('remoteUrl');
243
- if (!remote) { alert('Set remote first'); return; }
244
- const token = prompt('Personal access token (will not be stored).');
245
- if (!token) return;
246
- status('pulling...');
247
- try {
248
- await git.addRemote({ fs, dir: repoDir, remote: 'origin', url: remote }).catch(()=>{});
249
- await git.pull({
250
- fs, http: git.http, dir: repoDir,
251
- remote: 'origin', ref: 'main',
252
- singleBranch: true,
253
- author: { name: 'Browser User', email: 'user@local' },
254
- onAuth: () => ({ username: 'x-access-token', password: token })
255
- });
256
- status('pull complete');
257
- await refreshTree();
258
- } catch (e) {
259
- console.error(e);
260
- status('pull failed: ' + e.message);
261
- }
262
- }
263
-
264
- // Helpers
265
- function status(text) { statusEl.textContent = text; }
266
- function log(text) { logEl.textContent = (new Date()).toLocaleTimeString() + ' — ' + text + '\n' + logEl.textContent; }
267
-
268
- // Wire UI
269
- document.getElementById('initRepo').onclick = initRepo;
270
- document.getElementById('refreshTree').onclick = refreshTree;
271
- document.getElementById('saveCommit').onclick = saveAndCommit;
272
- document.getElementById('logBtn').onclick = showLog;
273
- document.getElementById('newFile').onclick = async () => {
274
- const name = prompt('New file path (e.g. objects/note.md)');
275
- if (!name) return;
276
- filepathInput.value = name;
277
- editor.value = '# ' + name.split('/').pop();
278
- };
279
- document.getElementById('newFolder').onclick = async () => {
280
- const name = prompt('New folder path (e.g. objects/notes)');
281
- if (!name) return;
282
- const full = repoDir + '/' + name;
283
- try { await pfs.mkdir(full, { recursive: true }); status('folder created'); await refreshTree(); } catch(e){status('mkdir error')}
284
- };
285
- document.getElementById('setRemote').onclick = setRemote;
286
- document.getElementById('pushBtn').onclick = push;
287
- document.getElementById('pullBtn').onclick = pull;
288
-
289
- // Live preview
290
- editor.addEventListener('input', () => {
291
- preview.innerHTML = marked.parse(editor.value || '');
292
- });
293
-
294
- // Register service worker
295
- if ('serviceWorker' in navigator) {
296
- navigator.serviceWorker.register('sw.js').then(()=>console.log('sw registered')).catch(()=>console.warn('sw failed'));
297
- }
298
-
299
- // Auto init on load
300
- await initRepo();
301
- await refreshTree();
302
- status('ready');
303
- </script>
304
-</body>
305
-</html>