marky

Lightweight bookmarking + plaintext archive
Log | Files | Refs | README | LICENSE

commit 49bb35851c8bcc6363f34b833ba217cc4b7443ce
parent 82c12605661bb0fa89aff760158c04d6c4a2a79d
Author: David Eisinger <[email protected]>
Date:   Mon, 13 Oct 2025 23:36:16 -0400

Remove old index.html

Diffstat:
Minternal/static.go | 39++++-----------------------------------
Dinternal/web/index.html | 166-------------------------------------------------------------------------------
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>