commit 49bb35851c8bcc6363f34b833ba217cc4b7443ce
parent 82c12605661bb0fa89aff760158c04d6c4a2a79d
Author: David Eisinger <[email protected]>
Date: Mon, 13 Oct 2025 23:36:16 -0400
Remove old index.html
Diffstat:
2 files changed, 4 insertions(+), 201 deletions(-)
diff --git a/internal/static.go b/internal/static.go
@@ -14,8 +14,8 @@ import (
//go:embed web/*
var webAssets embed.FS
-// StaticHandler returns an http.Handler serving the embedded web UI with
-// history-fallback to index.html for client-side routes like "/{username}".
+// StaticHandler returns an http.Handler serving the embedded web UI files only.
+// Unknown paths return 404 (no SPA fallback).
func StaticHandler() (http.Handler, error) { return StaticHandlerWithConfig(HandlerConfig{}) }
// StaticHandlerWithConfig serves the embedded UI, injecting runtime config.
@@ -26,46 +26,15 @@ func StaticHandlerWithConfig(cfg HandlerConfig) (http.Handler, error) {
}
fsys := http.FS(sub)
fileServer := http.FileServer(fsys)
- indexBytes, err := fs.ReadFile(sub, "index.html")
- if err != nil {
- return nil, err
- }
- // Inject shared header into index.html placeholder
- if hb, err := fs.ReadFile(sub, "header.html"); err == nil {
- if bytes.Contains(indexBytes, []byte("{{HEADER}}")) {
- indexBytes = bytes.ReplaceAll(indexBytes, []byte("{{HEADER}}"), hb)
- }
- }
- // Inject a small config script when features are toggled.
- if cfg.DisableSignup {
- marker := []byte("<script src=\"/app.js\"></script>")
- if i := bytes.Index(indexBytes, marker); i >= 0 {
- inject := []byte("<script>window.MARKY_CFG={disableSignup:true}</script>\n")
- nb := make([]byte, 0, len(indexBytes)+len(inject))
- nb = append(nb, indexBytes[:i]...)
- nb = append(nb, inject...)
- nb = append(nb, indexBytes[i:]...)
- indexBytes = nb
- }
- }
- // Apply cache-busting query params to static asset URLs
- indexBytes = applyCacheBust(indexBytes)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, "/")
- if p == "" {
- // Serve index directly to avoid redirects
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- _, _ = w.Write(indexBytes)
- return
- }
// Try to stat the requested file
if f, err := fs.Stat(sub, path.Clean(p)); err == nil && !f.IsDir() {
fileServer.ServeHTTP(w, r)
return
}
- // Fallback to index.html for SPA routes
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- _, _ = w.Write(indexBytes)
+ // Unknown file: 404
+ http.NotFound(w, r)
}), nil
}
diff --git a/internal/web/index.html b/internal/web/index.html
@@ -1,166 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>marky</title>
- <link rel="stylesheet" href="/styles.css" />
- <script src="/vendor/vue.global.prod.js"></script>
- <script>if(!window.Vue){var s=document.createElement('script');s.src='https://unpkg.com/vue@3/dist/vue.global.prod.js';document.head.appendChild(s)}</script>
- <script src="/vendor/marked.min.js"></script>
- <script>if(!window.marked){var s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/marked/marked.min.js';document.head.appendChild(s)}</script>
- <script src="/vendor/purify.min.js"></script>
- <script>if(!window.DOMPurify){var s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js';document.head.appendChild(s)}</script>
- </head>
- <body>
- <div id="app">
- {{HEADER}}
-
- <main class="container grid">
- <section v-if="view==='login'">
- <h2>Login</h2>
- <form @submit.prevent="login">
- <label>Username <input v-model.trim="auth.username" autocomplete="username" required /></label>
- <label>Password <input v-model="auth.password" type="password" autocomplete="current-password" required /></label>
- <div class="actions">
- <button :disabled="busy">{{ busy ? 'Working…' : 'Login' }}</button>
- </div>
- <p v-if="error" class="error">{{ error }}</p>
- <p class="auth-links" v-if="!disableSignup">No account? <a href="#" @click.prevent="view='signup'">Create one</a></p>
- </form>
- </section>
-
- <section v-if="view==='signup' && !disableSignup">
- <h2>Sign Up</h2>
- <form @submit.prevent="signup">
- <label>Username <input v-model.trim="auth.username" autocomplete="username" required /></label>
- <label>Password <input v-model="auth.password" type="password" autocomplete="new-password" required /></label>
- <div class="actions">
- <button :disabled="busy">{{ busy ? 'Working…' : 'Create Account' }}</button>
- </div>
- <p v-if="error" class="error">{{ error }}</p>
- <p class="auth-links">Have an account? <a href="#" @click.prevent="view='login'">Log in</a></p>
- </form>
- </section>
-
- <section v-if="view==='self'">
- <h2>Add Bookmark</h2>
- <form @submit.prevent="createBookmark">
- <label>
- URL
- <input v-model.trim="form.url" type="url" required placeholder="https://example.com" />
- </label>
- <label>
- Title (optional)
- <input v-model.trim="form.title" type="text" placeholder="Page title" />
- </label>
- <label>
- Description (optional)
- <textarea v-model.trim="form.description" rows="2" placeholder="Notes..."></textarea>
- </label>
- <label>
- Referrer (optional)
- <input v-model.trim="form.referrer" type="url" placeholder="https://site-that-linked-here.example" />
- </label>
- <label class="inline">
- <input type="checkbox" v-model="form.public" /> Public
- </label>
- <button :disabled="busy">{{ busy ? 'Working…' : 'Save' }}</button>
- <p v-if="error" class="error">{{ error }}</p>
- <p v-if="message" class="ok">{{ message }}</p>
- </form>
- </section>
-
- <section v-if="view==='self'" id="bookmarks">
- <h2>
- Bookmarks
- <span class="small-links" v-if="me && me.username">
- <a :href="`/u:${me.username}.json`" title="Your JSON feed (includes private if logged in)">JSON</a>
- <span class="sep">|</span>
- <a :href="`/u:${me.username}.rss`" title="Your RSS feed (includes private if logged in)">RSS</a>
- </span>
- </h2>
- <form method="GET" :action="me && me.username ? `/u:${me.username}` : `/`">
- <input name="q" type="search" placeholder="Search…" :value="pager && pager.query ? pager.query : ''" />
- <input type="hidden" name="vis" :value="pager && pager.vis ? pager.vis : 'all'" />
- <button>Search</button>
- </form>
- <p v-if="pager" class="muted results-summary">
- <span>
- Displaying {{ (pager.count||0) === 0 ? 0 : ((pager.page-1)*pager.size + 1) }}-{{ (pager.count||0) === 0 ? 0 : ((pager.page-1)*pager.size + results.length) }} of {{ pager.count || 0 }} bookmarks
- </span>
- <span class="results-filter" v-if="pager && pager.owner">
- <a :class="{ selected: (pager.vis||'all')==='all' }" :href="pager && pager.query ? `/u:${me.username}?page=1&size=${pager.size}&q=${pager.queryEncoded}&vis=all#bookmarks` : `/u:${me.username}?page=1&size=${pager.size}&vis=all#bookmarks`">All</a>
- <span class="sep">|</span>
- <a :class="{ selected: (pager.vis||'all')==='public' }" :href="pager && pager.query ? `/u:${me.username}?page=1&size=${pager.size}&q=${pager.queryEncoded}&vis=public#bookmarks` : `/u:${me.username}?page=1&size=${pager.size}&vis=public#bookmarks`">Public</a>
- <span class="sep">|</span>
- <a :class="{ selected: (pager.vis||'all')==='private' }" :href="pager && pager.query ? `/u:${me.username}?page=1&size=${pager.size}&q=${pager.queryEncoded}&vis=private#bookmarks` : `/u:${me.username}?page=1&size=${pager.size}&vis=private#bookmarks`">Private</a>
- </span>
- </p>
- <div class="results">
- <article v-for="b in results" :key="b.id" :class="['card', { private: !b.public }]">
- <h3>
- <a :href="b.url" target="_blank" rel="noopener noreferrer">{{ b.title || b.url }}</a>
- </h3>
- <p :class="['url', { truncate: b.urlTrunc }]" :title="b.url" @click="toggleURL(b)">{{ b.url }}</p>
- <p v-if="b.referrer" class="referrer">Referrer: <a :href="b.referrer" target="_blank" rel="noopener noreferrer">{{ b.referrer }}</a></p>
- <div v-if="b.editing" class="edit-form">
- <label>URL <input v-model.trim="b.editUrl" type="url" required /></label>
- <label>Title <input v-model.trim="b.editTitle" type="text" required /></label>
- <label>Description <textarea v-model.trim="b.editDescription" rows="2"></textarea></label>
- <label>Referrer <input v-model.trim="b.editReferrer" type="url" placeholder="https://site-that-linked-here.example" /></label>
- <label class="inline"><input type="checkbox" v-model="b.editPublic" /> Public</label>
- <div class="actions">
- <button @click="saveEdit(b)" :disabled="b.saving">{{ b.saving ? 'Saving…' : 'Save' }}</button>
- <button @click="cancelEdit(b)">Cancel</button>
- </div>
- </div>
- <p v-else-if="b.excerpt" class="excerpt" v-html="formatExcerpt(b.excerpt)"></p>
- <div v-if="!b.editing && !b.excerpt && b.description" class="desc" v-html="renderMarkdown(b.description)"></div>
- <p class="action-links">
- <span class="meta">{{ formatDate(b.updated_at) }}</span>
- <span class="sep">|</span>
- <template v-if="b.has_archive">
- <a :href="`/bookmarks/${b.id}/archive`" target="_blank" title="View archived text">Archive</a>
- <span class="sep">|</span>
- </template>
- <a href="#" @click.prevent="startEdit(b)">Edit</a>
- <span class="sep">|</span>
- <a href="#" @click.prevent="confirmDelete(b)">Delete</a>
- </p>
-
- </article>
- </div>
- </section>
-
- <section v-if="view==='public'" class="span2">
- <h2>
- {{ viewUser }}
- <span class="small-links">
- <a :href="`/u:${viewUser}.json`" title="User JSON feed">JSON</a>
- <span class="sep">|</span>
- <a :href="`/u:${viewUser}.rss`" title="User RSS feed">RSS</a>
- </span>
- </h2>
- <div class="results">
- <article v-for="b in results" :key="b.id" class="card">
- <h3><a :href="b.url" target="_blank" rel="noopener noreferrer">{{ b.title || b.url }}</a></h3>
- <p :class="['url', { truncate: b.urlTrunc }]" :title="b.url" @click="toggleURL(b)">{{ b.url }}</p>
- <p v-if="b.referrer" class="referrer">Referrer: <a :href="b.referrer" target="_blank" rel="noopener noreferrer">{{ b.referrer }}</a></p>
- <div v-if="b.description" class="desc" v-html="renderMarkdown(b.description)"></div>
- <p class="action-links">
- <span class="meta">{{ formatDate(b.updated_at) }}</span>
- <template v-if="b.has_archive">
- <span class="sep">|</span>
- <a :href="`/bookmarks/${b.id}/archive`" target="_blank">Archive</a>
- </template>
- </p>
- </article>
- </div>
- </section>
- </main>
- </div>
-
- <script src="/app.js"></script>
- </body>
- </html>