Browse Source

added push-all to the cli

implement black theme
implement swagger API docs

Nostr-Signature: c15ce3d2f1ae613492802533a7e71b96df919a2ff52d501630c6ee64abf6a718 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc ba72d348528a2846c5e44474af821e79fcdb377caa0d905ae7e7fc9115b77ea92f65325a8f0000f83467983068f643ecf3beaca784666d361a8883160ae3a936
main
Silberengel 3 weeks ago
parent
commit
bb4d8b8161
  1. 2
      gitrepublic-cli
  2. 1
      nostr/commit-signatures.jsonl
  3. 162
      package-lock.json
  4. 3
      package.json
  5. 48
      src/app.css
  6. 8
      src/app.html
  7. 2
      src/hooks.server.ts
  8. 1
      src/lib/components/NavBar.svelte
  9. 200
      src/lib/components/ThemeToggle.svelte
  10. 2
      src/lib/services/git/file-manager.ts
  11. 45
      src/routes/+layout.svelte
  12. 130
      src/routes/api-docs/+page.svelte
  13. 12
      src/routes/api/openapi.json/+server.ts
  14. 1112
      src/routes/api/openapi.json/openapi.json
  15. 2
      static/swagger-ui/swagger-ui-bundle.js
  16. 2
      static/swagger-ui/swagger-ui-standalone-preset.js
  17. 3
      static/swagger-ui/swagger-ui.css

2
gitrepublic-cli

@ -1 +1 @@
Subproject commit be480332ebd06991a3a88e22aa2d175596e20337 Subproject commit daafc006e76eb25bf64b5a65901729faaea2d2eb

1
nostr/commit-signatures.jsonl

@ -1 +1,2 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771497264,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","update docs"]],"content":"Signed commit: update docs","id":"5a14564a2b82b3b4ee4e21d28e7b362cc82e3c27eac38691c85f46480b100cf1","sig":"d1369aff4db39f61aba5f0954c0c8ba92df4aec96f1fab7cc5af51d1b0667734f35dec99363290de2c248b7074369f592b238b1b66987e09f267062073167131"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771497264,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","update docs"]],"content":"Signed commit: update docs","id":"5a14564a2b82b3b4ee4e21d28e7b362cc82e3c27eac38691c85f46480b100cf1","sig":"d1369aff4db39f61aba5f0954c0c8ba92df4aec96f1fab7cc5af51d1b0667734f35dec99363290de2c248b7074369f592b238b1b66987e09f267062073167131"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771497680,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","validate signatures"]],"content":"Signed commit: validate signatures","id":"47edd2e8cbea27854a429202ddfb3fde3531a355276c619258bc90c4d6ce54cc","sig":"a941abf1d2c8e7dae4d5b4d6424c2e5394b05c98898d88b7acc1501cd6d8d3d13aea8be8d797dcb0701f752a32bf72a3b02f3c814707e10ed18d6d24f11d8ae0"}

162
package-lock.json generated

@ -115,18 +115,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/@asciidoctor/opal-runtime/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@ -937,30 +925,6 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.57.1", "version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
@ -987,30 +951,6 @@
"node": ">=10.10.0" "node": ">=10.10.0"
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@humanwhocodes/module-importer": { "node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -2338,10 +2278,13 @@
} }
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==",
"license": "MIT" "license": "MIT",
"engines": {
"node": "20 || >=22"
}
}, },
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
@ -2357,12 +2300,15 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
} }
}, },
"node_modules/braces": { "node_modules/braces": {
@ -2583,13 +2529,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/constantinople": { "node_modules/constantinople": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
@ -2962,30 +2901,6 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/esm-env": { "node_modules/esm-env": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
@ -3195,18 +3110,6 @@
"minimatch": "^5.0.1" "minimatch": "^5.0.1"
} }
}, },
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -3369,30 +3272,6 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/globals": { "node_modules/globals": {
"version": "13.24.0", "version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@ -3990,16 +3869,15 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.5", "version": "10.2.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
"dev": true, "license": "BlueOak-1.0.0",
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^5.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"

3
package.json

@ -56,7 +56,8 @@
"overrides": { "overrides": {
"ajv": "^8.17.1", "ajv": "^8.17.1",
"cookie": "^0.7.2", "cookie": "^0.7.2",
"esbuild": "^0.24.0" "esbuild": "^0.24.0",
"minimatch": "^10.0.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

48
src/app.css

@ -1,9 +1,11 @@
/* Import fonts - IBM Plex Serif for classic Roman feel with modern tech aesthetic */ /* Import fonts - IBM Plex Serif for classic Roman feel with modern tech aesthetic */
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap');
/* GitRepublic Theme - Light/Dark Mode with Royal Plum Palette */ /* GitRepublic Theme - Three Themes: Light, Dark (Purple), and Black */
:root { /* GitRepublic Light Theme */
:root,
[data-theme="light"] {
/* Light theme colors */ /* Light theme colors */
--royal-plum: #7B1E6D; --royal-plum: #7B1E6D;
--snow: #FBF7FA; --snow: #FBF7FA;
@ -43,6 +45,7 @@
--warning-text: #6a3000; /* Darker for better contrast */ --warning-text: #6a3000; /* Darker for better contrast */
} }
/* GitRepublic Dark Theme (Purple) - Default */
[data-theme="dark"] { [data-theme="dark"] {
/* Dark theme colors - darker versions of the palette */ /* Dark theme colors - darker versions of the palette */
--royal-plum: #9a2a7f; --royal-plum: #9a2a7f;
@ -83,6 +86,47 @@
--warning-text: #ffcc44; /* Brighter for better contrast */ --warning-text: #ffcc44; /* Brighter for better contrast */
} }
/* GitRepublic Black Theme - GitHub-style all black */
[data-theme="black"] {
/* Black theme colors - GitHub-inspired */
--royal-plum: #9a2a7f;
--snow: #0d1117; /* GitHub's black background */
--lavender-blush: #161b22; /* GitHub's slightly lighter black */
--thistle: #21262d; /* GitHub's border color */
--lilac: #30363d; /* GitHub's hover color */
/* Black theme semantic colors */
--bg-primary: #0d1117; /* Pure black background */
--bg-secondary: #161b22; /* Slightly lighter for cards */
--bg-tertiary: #21262d; /* Even lighter for nested elements */
--text-primary: #f0f6fc; /* GitHub's primary text color */
--text-secondary: #c9d1d9; /* GitHub's secondary text color */
--text-muted: #8b949e; /* GitHub's muted text color */
--border-color: #30363d; /* GitHub's border color */
--border-light: #21262d; /* Lighter border */
--accent: var(--royal-plum);
--accent-hover: #b84a8a;
--accent-light: #6a3a5a;
--accent-text: #ffffff;
--link-color: #58a6ff; /* GitHub's link color */
--link-hover: #79c0ff; /* GitHub's link hover */
--card-bg: #161b22; /* GitHub's card background */
--card-border: #30363d; /* GitHub's card border */
--button-primary: var(--royal-plum);
--button-primary-hover: #b84a8a;
--button-secondary: #21262d; /* GitHub's secondary button */
--button-secondary-hover: #30363d; /* GitHub's secondary button hover */
--input-bg: #0d1117; /* GitHub's input background */
--input-border: #30363d; /* GitHub's input border */
--input-focus: var(--royal-plum);
--error-bg: #3d1f1f;
--error-text: #f85149; /* GitHub's error color */
--success-bg: #1a3a2a;
--success-text: #3fb950; /* GitHub's success color */
--warning-bg: #3d2f1f;
--warning-text: #d29922; /* GitHub's warning color */
}
/* Base styles */ /* Base styles */
* { * {
box-sizing: border-box; box-sizing: border-box;

8
src/app.html

@ -9,10 +9,12 @@
// Apply theme immediately to prevent flash // Apply theme immediately to prevent flash
(function() { (function() {
const savedTheme = localStorage.getItem('theme'); const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') { if (savedTheme === 'gitrepublic-light') {
// Light theme is default, no attribute needed document.documentElement.setAttribute('data-theme', 'light');
} else if (savedTheme === 'gitrepublic-black') {
document.documentElement.setAttribute('data-theme', 'black');
} else { } else {
// Default to dark // Default to gitrepublic-dark (purple)
document.documentElement.setAttribute('data-theme', 'dark'); document.documentElement.setAttribute('data-theme', 'dark');
} }
})(); })();

2
src/hooks.server.ts

@ -127,7 +127,9 @@ export const handle: Handle = async ({ event, resolve }) => {
const csp = [ const csp = [
"default-src 'self'", "default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // unsafe-eval needed for Svelte "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // unsafe-eval needed for Svelte
"script-src-elem 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:", "img-src 'self' data: https:",
"font-src 'self' data: https://fonts.gstatic.com", "font-src 'self' data: https://fonts.gstatic.com",
"connect-src 'self' wss: https:", "connect-src 'self' wss: https:",

1
src/lib/components/NavBar.svelte

@ -160,6 +160,7 @@
<a href="/search" class:active={isActive('/search')} onclick={closeMobileMenu}>Search</a> <a href="/search" class:active={isActive('/search')} onclick={closeMobileMenu}>Search</a>
<a href="/signup" class:active={isActive('/signup')} onclick={closeMobileMenu}>Register</a> <a href="/signup" class:active={isActive('/signup')} onclick={closeMobileMenu}>Register</a>
<a href="/docs" class:active={isActive('/docs')} onclick={closeMobileMenu}>Docs</a> <a href="/docs" class:active={isActive('/docs')} onclick={closeMobileMenu}>Docs</a>
<a href="/api-docs" class:active={isActive('/api-docs')} onclick={closeMobileMenu}>API Docs</a>
</div> </div>
</nav> </nav>
<div class="auth-section"> <div class="auth-section">

200
src/lib/components/ThemeToggle.svelte

@ -4,14 +4,23 @@
// Get theme and toggle function from layout context // Get theme and toggle function from layout context
const themeContext = getContext<{ const themeContext = getContext<{
theme: { value: 'light' | 'dark' }; theme: { value: 'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black' };
toggleTheme: () => void; toggleTheme: () => void;
}>('theme'); }>('theme');
let currentTheme: 'light' | 'dark' = 'dark'; let currentTheme = $state<'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black'>('gitrepublic-dark');
let dropdownOpen = $state(false);
let buttonElement: HTMLButtonElement | null = $state(null);
function updateTheme() { function updateTheme() {
currentTheme = document.documentElement.hasAttribute('data-theme') ? 'dark' : 'light'; const themeAttr = document.documentElement.getAttribute('data-theme');
if (themeAttr === 'light') {
currentTheme = 'gitrepublic-light';
} else if (themeAttr === 'black') {
currentTheme = 'gitrepublic-black';
} else {
currentTheme = 'gitrepublic-dark'; // default to dark/purple
}
} }
onMount(() => { onMount(() => {
@ -28,51 +37,98 @@
attributeFilter: ['data-theme'] attributeFilter: ['data-theme']
}); });
return () => observer.disconnect(); // Close dropdown when clicking outside
function handleClickOutside(event: MouseEvent) {
if (buttonElement && !buttonElement.contains(event.target as Node) &&
!(event.target as Element)?.closest('.theme-dropdown')) {
dropdownOpen = false;
}
}
document.addEventListener('click', handleClickOutside);
return () => {
observer.disconnect();
document.removeEventListener('click', handleClickOutside);
};
}); });
function handleToggle() { function toggleDropdown() {
if (themeContext) { dropdownOpen = !dropdownOpen;
themeContext.toggleTheme(); }
function selectTheme(theme: 'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black') {
// Set theme directly
if (theme === 'gitrepublic-light') {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'gitrepublic-light');
} else if (theme === 'gitrepublic-black') {
document.documentElement.setAttribute('data-theme', 'black');
localStorage.setItem('theme', 'gitrepublic-black');
} else { } else {
// Fallback: toggle manually document.documentElement.setAttribute('data-theme', 'dark');
const isDark = document.documentElement.hasAttribute('data-theme'); localStorage.setItem('theme', 'gitrepublic-dark');
if (isDark) {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.setAttribute('data-theme', 'dark');
}
localStorage.setItem('theme', isDark ? 'light' : 'dark');
} }
updateTheme(); updateTheme();
dropdownOpen = false;
}
function getThemeName(theme: 'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black'): string {
if (theme === 'gitrepublic-light') return 'Light';
if (theme === 'gitrepublic-black') return 'Black';
return 'Purple';
} }
</script> </script>
<button class="theme-toggle" onclick={handleToggle} title={currentTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}> <div class="theme-toggle-container">
<span class="theme-toggle-icon"> <button
{#if currentTheme === 'dark'} class="theme-toggle"
<!-- Sun icon for light mode --> onclick={toggleDropdown}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> title="Theme settings"
<circle cx="12" cy="12" r="5"></circle> bind:this={buttonElement}
<line x1="12" y1="1" x2="12" y2="3"></line> >
<line x1="12" y1="21" x2="12" y2="23"></line> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <circle cx="12" cy="12" r="3"></circle>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24"></path>
<line x1="1" y1="12" x2="3" y2="12"></line> </svg>
<line x1="21" y1="12" x2="23" y2="12"></line> </button>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> {#if dropdownOpen}
</svg> <div class="theme-dropdown">
{:else} <button
<!-- Moon icon for dark mode --> class="theme-option"
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> class:active={currentTheme === 'gitrepublic-light'}
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> onclick={() => selectTheme('gitrepublic-light')}
</svg> >
{/if} <span class="theme-circle"></span>
</span> <span class="theme-name">Light</span>
</button> </button>
<button
class="theme-option"
class:active={currentTheme === 'gitrepublic-dark'}
onclick={() => selectTheme('gitrepublic-dark')}
>
<span class="theme-circle">🟣</span>
<span class="theme-name">Purple</span>
</button>
<button
class="theme-option"
class:active={currentTheme === 'gitrepublic-black'}
onclick={() => selectTheme('gitrepublic-black')}
>
<span class="theme-circle"></span>
<span class="theme-name">Black</span>
</button>
</div>
{/if}
</div>
<style> <style>
.theme-toggle-container {
position: relative;
display: inline-block;
}
.theme-toggle { .theme-toggle {
cursor: pointer; cursor: pointer;
padding: 0.5rem; padding: 0.5rem;
@ -100,16 +156,72 @@
transform: scale(0.98); transform: scale(0.98);
} }
.theme-toggle-icon { .theme-toggle svg {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.theme-toggle-icon svg { .theme-dropdown {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 0.375rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
min-width: 150px;
z-index: 1000;
overflow: hidden;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.theme-option {
width: 100%; width: 100%;
height: 100%; padding: 0.75rem 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
background: transparent;
border: none;
color: var(--text-primary);
cursor: pointer;
transition: background 0.2s ease;
font-family: 'IBM Plex Serif', serif;
font-size: 0.875rem;
text-align: left;
}
.theme-option:hover {
background: var(--bg-secondary);
}
.theme-option.active {
background: var(--bg-tertiary);
font-weight: 600;
}
.theme-option .theme-circle {
font-size: 1.25rem;
line-height: 1;
display: inline-block;
}
.theme-option.active .theme-circle {
transform: scale(1.1);
}
.theme-name {
flex: 1;
} }
</style> </style>

2
src/lib/services/git/file-manager.ts

@ -4,7 +4,7 @@
*/ */
import simpleGit, { type SimpleGit } from 'simple-git'; import simpleGit, { type SimpleGit } from 'simple-git';
import { readFile, readdir, stat } from 'fs/promises'; import { readdir } from 'fs/promises';
import { join, dirname, normalize, resolve } from 'path'; import { join, dirname, normalize, resolve } from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { spawn } from 'child_process'; import { spawn } from 'child_process';

45
src/routes/+layout.svelte

@ -14,8 +14,8 @@
// Accept children as a snippet prop (Svelte 5) // Accept children as a snippet prop (Svelte 5)
let { children }: { children: Snippet } = $props(); let { children }: { children: Snippet } = $props();
// Theme management - default to dark // Theme management - default to gitrepublic-dark (purple)
let theme: 'light' | 'dark' = 'dark'; let theme: 'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black' = 'gitrepublic-dark';
// User level checking state // User level checking state
let checkingUserLevel = $state(false); let checkingUserLevel = $state(false);
@ -24,24 +24,16 @@
// Only run client-side code // Only run client-side code
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
// Check for saved theme preference or default to dark // Check for saved theme preference or default to gitrepublic-dark
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; const savedTheme = localStorage.getItem('theme') as 'gitrepublic-light' | 'gitrepublic-dark' | 'gitrepublic-black' | null;
if (savedTheme === 'light' || savedTheme === 'dark') { if (savedTheme === 'gitrepublic-light' || savedTheme === 'gitrepublic-dark' || savedTheme === 'gitrepublic-black') {
theme = savedTheme; theme = savedTheme;
} else { } else {
// Default to dark // Default to gitrepublic-dark (purple)
theme = 'dark'; theme = 'gitrepublic-dark';
} }
applyTheme(); applyTheme();
// Watch for system theme changes (only if user hasn't set a preference)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
theme = e.matches ? 'dark' : 'light';
applyTheme();
}
});
// Check for session expiry (24 hours) // Check for session expiry (24 hours)
if (isSessionExpired()) { if (isSessionExpired()) {
// Session expired - logout user // Session expired - logout user
@ -133,16 +125,31 @@
} }
function applyTheme() { function applyTheme() {
if (theme === 'dark') { // Remove all theme attributes first
document.documentElement.removeAttribute('data-theme');
document.documentElement.removeAttribute('data-theme-light');
document.documentElement.removeAttribute('data-theme-black');
// Apply the selected theme
if (theme === 'gitrepublic-light') {
document.documentElement.setAttribute('data-theme', 'light');
} else if (theme === 'gitrepublic-dark') {
document.documentElement.setAttribute('data-theme', 'dark'); document.documentElement.setAttribute('data-theme', 'dark');
} else { } else if (theme === 'gitrepublic-black') {
document.documentElement.removeAttribute('data-theme'); document.documentElement.setAttribute('data-theme', 'black');
} }
localStorage.setItem('theme', theme); localStorage.setItem('theme', theme);
} }
function toggleTheme() { function toggleTheme() {
theme = theme === 'light' ? 'dark' : 'light'; // Cycle through themes: gitrepublic-dark -> gitrepublic-light -> gitrepublic-black -> gitrepublic-dark
if (theme === 'gitrepublic-dark') {
theme = 'gitrepublic-light';
} else if (theme === 'gitrepublic-light') {
theme = 'gitrepublic-black';
} else {
theme = 'gitrepublic-dark';
}
applyTheme(); applyTheme();
} }

130
src/routes/api-docs/+page.svelte

@ -0,0 +1,130 @@
<script lang="ts">
import { onMount } from 'svelte';
onMount(() => {
// Load Swagger UI from local static files
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '/swagger-ui/swagger-ui.css';
document.head.appendChild(link);
let bundleScript: HTMLScriptElement | null = null;
// Load standalone preset first
const presetScript = document.createElement('script');
presetScript.src = '/swagger-ui/swagger-ui-standalone-preset.js';
presetScript.onload = () => {
// Then load the bundle
bundleScript = document.createElement('script');
bundleScript.src = '/swagger-ui/swagger-ui-bundle.js';
bundleScript.onload = () => {
// @ts-ignore - SwaggerUIBundle is loaded from static files
const ui = window.SwaggerUIBundle({
url: '/api/openapi.json',
dom_id: '#swagger-ui',
presets: [
// @ts-ignore
window.SwaggerUIBundle.presets.apis,
// @ts-ignore
window.SwaggerUIBundle.presets.standalone
],
layout: 'StandaloneLayout',
deepLinking: true,
displayRequestDuration: true,
tryItOutEnabled: true,
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
validatorUrl: null,
docExpansion: 'list',
filter: true,
showExtensions: true,
showCommonExtensions: true
});
};
document.head.appendChild(bundleScript);
};
document.head.appendChild(presetScript);
return () => {
// Cleanup
if (link.parentNode) document.head.removeChild(link);
if (presetScript.parentNode) document.head.removeChild(presetScript);
if (bundleScript?.parentNode) document.head.removeChild(bundleScript);
};
});
</script>
<div class="api-docs-container">
<div class="api-docs-header">
<h1>GitRepublic API Documentation</h1>
<p>Interactive API documentation with Swagger UI. All endpoints use NIP-98 HTTP authentication.</p>
<p class="note">
<strong>Note:</strong> To authenticate, you need to provide a NIP-98 Authorization header.
The format is: <code>Authorization: Nostr &lt;base64-encoded-event-json&gt;</code>
</p>
</div>
<div id="swagger-ui"></div>
</div>
<style>
.api-docs-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.api-docs-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.api-docs-header h1 {
margin: 0 0 0.5rem 0;
color: var(--text-primary);
}
.api-docs-header p {
margin: 0.5rem 0;
color: var(--text-secondary);
}
.api-docs-header .note {
margin-top: 1rem;
padding: 1rem;
background: var(--bg-secondary);
border-left: 3px solid var(--accent);
border-radius: 0.25rem;
}
.api-docs-header code {
background: var(--bg-tertiary);
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.875rem;
}
#swagger-ui {
margin-top: 2rem;
}
/* Override Swagger UI styles to match our theme */
:global(.swagger-ui) {
font-family: 'IBM Plex Serif', serif;
}
:global(.swagger-ui .topbar) {
display: none;
}
:global(.swagger-ui .info .title) {
color: var(--text-primary);
}
:global(.swagger-ui .scheme-container) {
background: var(--bg-secondary);
padding: 1rem;
border-radius: 0.375rem;
}
</style>

12
src/routes/api/openapi.json/+server.ts

@ -0,0 +1,12 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import openApiSpec from './openapi.json';
export const GET: RequestHandler = async () => {
return json(openApiSpec, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600'
}
});
};

1112
src/routes/api/openapi.json/openapi.json

File diff suppressed because it is too large Load Diff

2
static/swagger-ui/swagger-ui-bundle.js

File diff suppressed because one or more lines are too long

2
static/swagger-ui/swagger-ui-standalone-preset.js

File diff suppressed because one or more lines are too long

3
static/swagger-ui/swagger-ui.css

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save