Initial commit: SMF2290 studie-app for eksamen vår 2026

Komplett pensumstudie-app med:
- 120 flashcards, 49 quiz-spørsmål, 16 eksamenstrener-oppgaver
- Sammendrag av alle 12 forelesninger
- tl;dr-side for siste-minutts pugging
- Søk gjennom hele pensumet
- Dark/light mode, mobile-vennlig

Cross-platform launchere (Start.sh + Start.bat) med auto-detect
og auto-install av HTTP-server. PowerShell-fallback for Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 18:44:00 +02:00
commit 8933e9501d
52 changed files with 8796 additions and 0 deletions

116
server.ps1 Normal file
View File

@@ -0,0 +1,116 @@
# =====================================================================
# SMF2290 Pensum — server.ps1
# Lett statisk HTTP-server som fallback når Python/Node ikke er installert.
# Krever bare PowerShell 5+ (innebygd i Windows 10/11).
# =====================================================================
param(
[int]$Port = 8765,
[string]$Root = "."
)
Add-Type -AssemblyName System.Web
$RootAbs = (Resolve-Path -LiteralPath $Root).Path
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://127.0.0.1:$Port/")
try {
$listener.Start()
} catch {
Write-Host "Klarte ikke starte server pa port ${Port}: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
$mimeMap = @{
'.html' = 'text/html; charset=utf-8'
'.htm' = 'text/html; charset=utf-8'
'.css' = 'text/css; charset=utf-8'
'.js' = 'application/javascript; charset=utf-8'
'.mjs' = 'application/javascript; charset=utf-8'
'.json' = 'application/json; charset=utf-8'
'.md' = 'text/markdown; charset=utf-8'
'.txt' = 'text/plain; charset=utf-8'
'.svg' = 'image/svg+xml; charset=utf-8'
'.png' = 'image/png'
'.jpg' = 'image/jpeg'
'.jpeg' = 'image/jpeg'
'.gif' = 'image/gif'
'.ico' = 'image/x-icon'
'.webp' = 'image/webp'
'.woff' = 'font/woff'
'.woff2'= 'font/woff2'
'.ttf' = 'font/ttf'
'.otf' = 'font/otf'
'.pdf' = 'application/pdf'
}
Write-Host ""
Write-Host " Server kjorer pa http://localhost:$Port" -ForegroundColor Green
Write-Host " Trykk Ctrl+C for a stoppe" -ForegroundColor DarkGray
Write-Host ""
try {
while ($listener.IsListening) {
$context = $listener.GetContext()
$req = $context.Request
$res = $context.Response
try {
$path = [System.Web.HttpUtility]::UrlDecode($req.Url.LocalPath)
if ([string]::IsNullOrEmpty($path) -or $path -eq '/') { $path = '/index.html' }
# Strip leading slash and normalize
$relative = $path.TrimStart('/').Replace('/', [System.IO.Path]::DirectorySeparatorChar)
$filePath = Join-Path $RootAbs $relative
# Resolve to prevent directory traversal
try {
$resolved = [System.IO.Path]::GetFullPath($filePath)
} catch {
$resolved = $filePath
}
if (-not $resolved.StartsWith($RootAbs, [StringComparison]::OrdinalIgnoreCase)) {
$res.StatusCode = 403
$body = [System.Text.Encoding]::UTF8.GetBytes("403 Forbidden")
$res.OutputStream.Write($body, 0, $body.Length)
} elseif (Test-Path -LiteralPath $resolved -PathType Container) {
# Directory: try index.html
$idx = Join-Path $resolved 'index.html'
if (Test-Path -LiteralPath $idx -PathType Leaf) {
$bytes = [System.IO.File]::ReadAllBytes($idx)
$res.ContentType = 'text/html; charset=utf-8'
$res.ContentLength64 = $bytes.Length
$res.OutputStream.Write($bytes, 0, $bytes.Length)
} else {
$res.StatusCode = 404
$body = [System.Text.Encoding]::UTF8.GetBytes("404 Not Found")
$res.OutputStream.Write($body, 0, $body.Length)
}
} elseif (Test-Path -LiteralPath $resolved -PathType Leaf) {
$bytes = [System.IO.File]::ReadAllBytes($resolved)
$ext = [System.IO.Path]::GetExtension($resolved).ToLower()
$mime = if ($mimeMap.ContainsKey($ext)) { $mimeMap[$ext] } else { 'application/octet-stream' }
$res.ContentType = $mime
$res.ContentLength64 = $bytes.Length
$res.OutputStream.Write($bytes, 0, $bytes.Length)
} else {
$res.StatusCode = 404
$body = [System.Text.Encoding]::UTF8.GetBytes("404 Not Found: $path")
$res.OutputStream.Write($body, 0, $body.Length)
}
} catch {
try {
$res.StatusCode = 500
$body = [System.Text.Encoding]::UTF8.GetBytes("500 Internal Server Error: " + $_.Exception.Message)
$res.OutputStream.Write($body, 0, $body.Length)
} catch {}
} finally {
try { $res.Close() } catch {}
}
}
} finally {
try { $listener.Stop() } catch {}
try { $listener.Close() } catch {}
}